Revision 43020 trunk/org.gvsig.desktop/org.gvsig.desktop.compat.cdc/org.gvsig.fmap.dal/org.gvsig.fmap.dal.db/org.gvsig.fmap.dal.db.jdbc/src/main/java/org/gvsig/fmap/dal/store/jdbc/JDBCServerExplorer.java

View differences:

JDBCServerExplorer.java
1
/**
2
 * gvSIG. Desktop Geographic Information System.
3
 *
4
 * Copyright (C) 2007-2013 gvSIG Association.
5
 *
6
 * This program is free software; you can redistribute it and/or modify it under
7
 * the terms of the GNU General Public License as published by the Free Software
8
 * Foundation; either version 3 of the License, or (at your option) any later
9
 * version.
10
 *
11
 * This program is distributed in the hope that it will be useful, but WITHOUT
12
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
13
 * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more
14
 * details.
15
 *
16
 * You should have received a copy of the GNU General Public License along with
17
 * this program; if not, write to the Free Software Foundation, Inc., 51
18
 * Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
19
 *
20
 * For any additional information, do not hesitate to contact us at info AT
21
 * gvsig.com, or visit our website www.gvsig.com.
22
 */
23
package org.gvsig.fmap.dal.store.jdbc;
24

  
25
import java.sql.Connection;
26
import java.sql.DatabaseMetaData;
27
import java.sql.ResultSet;
28
import java.sql.SQLException;
29
import java.sql.Statement;
30
import java.util.ArrayList;
31
import java.util.Iterator;
32
import java.util.List;
33
import org.apache.commons.lang3.StringUtils;
34

  
35
import org.slf4j.Logger;
36
import org.slf4j.LoggerFactory;
37

  
38
import org.gvsig.fmap.dal.DALLocator;
39
import org.gvsig.fmap.dal.DataManager;
40
import org.gvsig.fmap.dal.DataStore;
41
import org.gvsig.fmap.dal.DataStoreParameters;
42
import org.gvsig.fmap.dal.NewDataStoreParameters;
43
import org.gvsig.fmap.dal.exception.CloseException;
44
import org.gvsig.fmap.dal.exception.DataException;
45
import org.gvsig.fmap.dal.exception.InitializeException;
46
import org.gvsig.fmap.dal.exception.OpenException;
47
import org.gvsig.fmap.dal.exception.ProviderNotRegisteredException;
48
import org.gvsig.fmap.dal.exception.ReadException;
49
import org.gvsig.fmap.dal.exception.RemoveException;
50
import org.gvsig.fmap.dal.exception.ValidateDataParametersException;
51
import org.gvsig.fmap.dal.feature.EditableFeatureType;
52
import org.gvsig.fmap.dal.feature.FeatureAttributeDescriptor;
53
import org.gvsig.fmap.dal.feature.FeatureType;
54
import org.gvsig.fmap.dal.resource.spi.ResourceProvider;
55
import org.gvsig.fmap.dal.serverexplorer.db.spi.AbstractDBServerExplorer;
56
import org.gvsig.fmap.dal.spi.DataManagerProviderServices;
57
import org.gvsig.fmap.dal.spi.DataServerExplorerProviderServices;
58
import org.gvsig.fmap.dal.store.jdbc.exception.JDBCExecuteSQLException;
59
import org.gvsig.fmap.dal.store.jdbc.exception.JDBCSQLException;
60
import org.gvsig.tools.exception.BaseException;
61

  
62
/**
63
 * @author jmvivo
64
 *
65
 */
66
public class JDBCServerExplorer extends AbstractDBServerExplorer
67
        implements JDBCHelperUser {
68

  
69
    private static final Logger LOG = LoggerFactory
70
            .getLogger(JDBCServerExplorer.class);
71

  
72
    private static final String METADATA_COLUMN_TABLE_CATALOG = "TABLE_CAT";
73
    private static final String METADATA_COLUMN_TABLE_SCHEMA = "TABLE_SCHEM";
74
    private static final String METADATA_COLUMN_TABLE_NAME = "TABLE_NAME";
75

  
76
    public static final String NAME = "JDBCServerExplorer";
77
    protected JDBCHelper helper;
78

  
79
    private Boolean canAdd;
80

  
81
    public JDBCServerExplorer(JDBCServerExplorerParameters parameters,
82
            DataServerExplorerProviderServices services)
83
            throws InitializeException {
84
        super(parameters, services);
85
        this.helper = createHelper();
86
    }
87

  
88
    protected JDBCServerExplorerParameters getJDBCParameters() {
89
        return (JDBCServerExplorerParameters) getParameters();
90
    }
91

  
92
    protected JDBCHelper createHelper() throws InitializeException {
93
        return new JDBCHelper(this, getJDBCParameters());
94
    }
95

  
96
    protected JDBCHelper getHelper() {
97
        return helper;
98
    }
99

  
100
    public List list() throws DataException {
101
        return this.list(MODE_ALL);
102
    }
103

  
104
    public List list(boolean showInformationDBTables) throws DataException {
105
        return this.list(MODE_ALL, showInformationDBTables);
106
    }
107

  
108
    public List list(int mode) throws DataException {
109
        JDBCServerExplorerParameters parameters = getJDBCParameters();
110
        if (parameters.getShowInformationDBTables() != null) {
111
            return this.list(mode, parameters.getShowInformationDBTables()
112
                    .booleanValue());
113
        }
114
        Boolean show = (Boolean) parameters
115
                .getDynClass()
116
                .getDynField(
117
                        JDBCServerExplorerParameters.SHOWINFORMATIONDBTABLES_PARAMTER_NAME)
118
                .getDefaultValue();
119
        if (show == null) {
120
            show = Boolean.FALSE;
121
        }
122

  
123
        return this.list(mode, show.booleanValue());
124
    }
125

  
126
    protected DataManagerProviderServices getManager() {
127
        return (DataManagerProviderServices) DALLocator.getDataManager();
128
    }
129

  
130
    public boolean hasGeometrySupport() {
131
        return false;
132
    }
133

  
134
    public boolean closeResourceRequested(ResourceProvider resource) {
135
        try {
136
            this.helper.close();
137
        } catch (CloseException e) {
138
            LOG.error("Exception in close Request", e);
139
        }
140
        return !this.helper.isOpen();
141
    }
142

  
143
    public void resourceChanged(ResourceProvider resource) {
144
        // Nothing to do
145
    }
146

  
147
    public void remove(DataStoreParameters dsp) throws RemoveException {
148
        final JDBCStoreParameters dsParams = (JDBCStoreParameters) dsp;
149

  
150
        TransactionalAction action = new TransactionalAction() {
151
            public boolean continueTransactionAllowed() {
152
                return false;
153
            }
154

  
155
            public Object action(Connection conn) throws DataException {
156
                Statement st;
157
                try {
158
                    st = conn.createStatement();
159
                } catch (SQLException e) {
160
                    throw new JDBCSQLException(e);
161
                }
162

  
163
                String sqlDrop = "Drop table "
164
                        + dsParams.tableID();
165

  
166
                try {
167
                    try {
168
                        JDBCHelper.execute(st, sqlDrop);
169
                    } catch (SQLException e) {
170
                        throw new JDBCExecuteSQLException(sqlDrop, e);
171
                    }
172

  
173
                } finally {
174
                    try {
175
                        st.close();
176
                    } catch (SQLException e) {
177
                    };
178
                }
179
                return null;
180
            }
181
        };
182
        try {
183
            this.helper.doConnectionAction(action);
184
        } catch (Exception e) {
185
            throw new RemoveException(this.getProviderName(), e);
186
        }
187
    }
188

  
189
    public DataStoreParameters getOpenParameters() throws DataException {
190
        JDBCServerExplorerParameters parameters = getJDBCParameters();
191
        JDBCStoreParameters params = new JDBCStoreParameters();
192
        params.setHost(parameters.getHost());
193
        params.setPort(parameters.getPort());
194
        params.setDBName(parameters.getDBName());
195
        params.setUser(parameters.getUser());
196
        params.setPassword(parameters.getPassword());
197
        params.setCatalog(parameters.getCatalog());
198
        params.setSchema(parameters.getSchema());
199
        params.setJDBCDriverClassName(parameters.getJDBCDriverClassName());
200
        params.setUrl(parameters.getUrl());
201
        return params;
202
    }
203

  
204
    public NewDataStoreParameters getAddParameters() throws DataException {
205
        JDBCServerExplorerParameters parameters = getJDBCParameters();
206
        JDBCNewStoreParameters params = new JDBCNewStoreParameters();
207
        params.setHost(parameters.getHost());
208
        params.setPort(parameters.getPort());
209
        params.setDBName(parameters.getDBName());
210
        params.setUser(parameters.getUser());
211
        params.setPassword(parameters.getPassword());
212
        params.setCatalog(parameters.getCatalog());
213
        params.setSchema(parameters.getSchema());
214
        params.setJDBCDriverClassName(parameters.getJDBCDriverClassName());
215
        params.setUrl(parameters.getUrl());
216

  
217
        params.setDefaultFeatureType(this.getServerExplorerProviderServices()
218
                .createNewFeatureType());
219

  
220
        return params;
221
    }
222

  
223
    public void closeDone() throws DataException {
224
        // Nothing to do
225
    }
226

  
227
    public void opendDone() throws DataException {
228
        // Nothin to do
229

  
230
    }
231

  
232
    public DataStore open(DataStoreParameters dsp) throws DataException {
233
        checkIsMine(dsp);
234
        DataManager dataMan = DALLocator.getDataManager();
235
        DataStore store;
236
        try {
237
            store = dataMan.openStore(dsp.getDataStoreName(), dsp);
238
        } catch (ValidateDataParametersException e) {
239
            throw new InitializeException(e);
240
        }
241

  
242
        return store;
243
    }
244

  
245
    protected void checkIsMine(DataStoreParameters dsp) {
246
        if (!(dsp instanceof JDBCConnectionParameters)) {
247
            // FIXME Exception ???
248
            throw new IllegalArgumentException(
249
                    "not instance of FilesystemStoreParameters");
250
        }
251

  
252
		// try {
253
        // dsp.validate();
254
        // } catch (ValidateDataParametersException e) {
255
        // throw new IllegalArgumentException("check parameters", e);
256
        // }
257
        JDBCServerExplorerParameters parameters = getJDBCParameters();
258

  
259
        JDBCConnectionParameters pgp = (JDBCConnectionParameters) dsp;
260
        if (!compare(pgp.getHost(), parameters.getHost())) {
261
            throw new IllegalArgumentException("wrong explorer: Host (mine: "
262
                    + parameters.getHost() + " other:" + pgp.getHost() + ")");
263
        }
264
        if (!compare(pgp.getPort(), parameters.getPort())) {
265
            throw new IllegalArgumentException("wrong explorer: Port (mine: "
266
                    + parameters.getPort() + " other:" + pgp.getPort() + ")");
267
        }
268
        if (!compare(pgp.getDBName(), parameters.getDBName())) {
269
            throw new IllegalArgumentException("wrong explorer: DBName (mine: "
270
                    + parameters.getDBName() + " other:" + pgp.getDBName()
271
                    + ")");
272
        }
273
        if (parameters.getCatalog() != null && !parameters.getCatalog().trim().equals("")) {
274
            // implicit catalog
275
            if (!compare(pgp.getCatalog(), parameters.getCatalog())) {
276
                throw new IllegalArgumentException(
277
                        "wrong explorer: Catalog (mine: "
278
                        + parameters.getCatalog() + " other:"
279
                        + pgp.getCatalog() + ")");
280
            }
281
        }
282
        if (parameters.getSchema() != null && !parameters.getSchema().trim().equals("")) {
283
            // implicit schema
284
            if (!compare(pgp.getSchema(), parameters.getSchema())) {
285
                throw new IllegalArgumentException(
286
                        "wrong explorer: Schema (mine: "
287
                        + parameters.getSchema() + " other:"
288
                        + pgp.getSchema() + ")");
289
            }
290
        }
291
    }
292

  
293
    protected boolean compare(Object str1, Object str2) {
294
        if (str1 == str2) {
295
            return true;
296
        }
297
        if (str1 == null) {
298
            return false;
299
        }
300
        return str1.equals(str2);
301
    }
302

  
303
    protected JDBCStoreParameters createStoreParams()
304
            throws InitializeException, ProviderNotRegisteredException {
305
        DataManagerProviderServices manager = this.getManager();
306
        JDBCServerExplorerParameters parameters = getJDBCParameters();
307
        JDBCStoreParameters orgParams = (JDBCStoreParameters) manager
308
                .createStoreParameters(getStoreName());
309
        orgParams.setHost(parameters.getHost());
310
        orgParams.setPort(parameters.getPort());
311
        orgParams.setDBName(parameters.getDBName());
312
        orgParams.setUser(parameters.getUser());
313
        orgParams.setPassword(parameters.getPassword());
314
        orgParams.setCatalog(parameters.getCatalog());
315
        orgParams.setJDBCDriverClassName(parameters.getJDBCDriverClassName());
316
        orgParams.setSchema(parameters.getSchema());
317
        orgParams.setUrl(parameters.getUrl());
318
        return orgParams;
319
    }
320

  
321
    public List list(final int mode, final boolean showInformationDBTables)
322
            throws DataException {
323

  
324
        final JDBCStoreParameters orgParams = createStoreParams();
325

  
326
        ConnectionAction action = new ConnectionAction() {
327

  
328
            public Object action(Connection conn) throws DataException {
329

  
330
                String[] tableTypes = null;
331
                if (!showInformationDBTables) {
332
                    tableTypes = new String[]{"TABLE", "VIEW"};
333
                }
334

  
335
                ResultSet result = null;
336
                try {
337
                    DatabaseMetaData metadata = conn.getMetaData();
338
                    result = metadata.getTables(null, null, null,
339
                            tableTypes);
340
                    List<JDBCStoreParameters> paramList = new ArrayList<JDBCStoreParameters>();
341
                    while (result.next()) {
342
                        JDBCStoreParameters params = (JDBCStoreParameters) orgParams
343
                                .getCopy();
344
                        params.setCatalog(result
345
                                .getString(METADATA_COLUMN_TABLE_CATALOG));
346
                        params.setSchema(result
347
                                .getString(METADATA_COLUMN_TABLE_SCHEMA));
348
                        params.setTable(result
349
                                .getString(METADATA_COLUMN_TABLE_NAME));
350
                        paramList.add(params);
351
                    }
352

  
353
                    return paramList;
354
                } catch (SQLException e) {
355
                    throw new JDBCSQLException(e);
356
                } finally {
357
                    if (result != null) {
358
                        try {
359
                            result.close();
360
                        } catch (Exception e) {
361
                            LOG.error("Error closing DatabaseMetadata "
362
                                    + "getTables() Resultset", e);
363
                        }
364
                    }
365
                }
366
            }
367

  
368
        };
369

  
370
        try {
371
            return (List) helper.doConnectionAction(action);
372
        } catch(JDBCSQLException e) {
373
            throw e;
374
        } catch (Exception e) {
375
            throw new ReadException(this.getProviderName(),e);
376
        }
377
    }
378

  
379
    public void open() throws OpenException {
380
        helper.open();
381
    }
382

  
383
    public void close() throws CloseException {
384
        helper.close();
385
    }
386

  
387
    @Override
388
    protected void doDispose() throws BaseException {
389
        helper.dispose();
390
        helper = null;
391
    }
392

  
393
    public String getProviderName() {
394
        return NAME;
395
    }
396

  
397
    public String getStoreName() {
398
        return JDBCStoreProvider.NAME;
399
    }
400

  
401
    public boolean canAdd() {
402
        if (this.canAdd == null) {
403
            ConnectionAction action = new ConnectionAction() {
404

  
405
                public Object action(Connection conn) throws DataException {
406
                    try {
407
                        DatabaseMetaData metadata = conn.getMetaData();
408
                        if (metadata.isReadOnly()) {
409
                            return Boolean.FALSE;
410
                        }
411
                        return Boolean.TRUE;
412
                    } catch (SQLException e) {
413
                        throw new JDBCSQLException(e);
414
                    }
415
                }
416

  
417
            };
418

  
419
            try {
420
                this.canAdd = (Boolean) helper.doConnectionAction(action);
421
            } catch (Exception e) {
422
                // FIXME Exception
423
                throw new RuntimeException(e);
424
            }
425
        }
426
        return this.canAdd.booleanValue();
427
    }
428

  
429
    public FeatureType getFeatureType(DataStoreParameters dsp)
430
            throws DataException {
431
        checkIsMine(dsp);
432

  
433
        // TODO: checks geometry columns and driver geometry supports
434
        EditableFeatureType edType = this.getServerExplorerProviderServices()
435
                .createNewFeatureType();
436
        helper.loadFeatureType(edType, (JDBCStoreParameters) dsp);
437

  
438
        return edType;
439

  
440
    }
441

  
442
    public boolean add(String providerName, NewDataStoreParameters ndsp, boolean overwrite)
443
            throws DataException {
444

  
445
        /**
446
         * CREATE [ [ GLOBAL | LOCAL ] { TEMPORARY | TEMP } ] TABLE table_name (
447
         * { column_name data_type [ DEFAULT default_expr ] [ column_constraint
448
         * [ ... ] ] | table_constraint | LIKE parent_table [ { INCLUDING |
449
         * EXCLUDING } DEFAULTS ] } [, ... ] ) [ INHERITS ( parent_table [, ...
450
         * ] ) ] [ WITH OIDS | WITHOUT OIDS ] [ ON COMMIT { PRESERVE ROWS |
451
         * DELETE ROWS | DROP } ]
452
         *
453
         * where column_constraint is:
454
         *
455
         * [ CONSTRAINT constraint_name ] { NOT NULL | NULL | UNIQUE | PRIMARY
456
         * KEY | CHECK (expression) | REFERENCES reftable [ ( refcolumn ) ] [
457
         * MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE action ] [ ON
458
         * UPDATE action ] } [ DEFERRABLE | NOT DEFERRABLE ] [ INITIALLY
459
         * DEFERRED | INITIALLY IMMEDIATE ]
460
         *
461
         * and table_constraint is:
462
         *
463
         * [ CONSTRAINT constraint_name ] { UNIQUE ( column_name [, ... ] ) |
464
         * PRIMARY KEY ( column_name [, ... ] ) | CHECK ( expression ) | FOREIGN
465
         * KEY ( column_name [, ... ] ) REFERENCES reftable [ ( refcolumn [, ...
466
         * ] ) ] [ MATCH FULL | MATCH PARTIAL | MATCH SIMPLE ] [ ON DELETE
467
         * action ] [ ON UPDATE action ] } [ DEFERRABLE | NOT DEFERRABLE ] [
468
         * INITIALLY DEFERRED | INITIALLY IMMEDIATE ]
469
         */
470
        if (!(ndsp instanceof JDBCNewStoreParameters)) {
471
            // FIXME exception
472
            throw new IllegalArgumentException();
473
        }
474
        checkIsMine(ndsp);
475

  
476
        JDBCNewStoreParameters jdbcnsp = (JDBCNewStoreParameters) ndsp;
477

  
478
        StringBuilder sql = new StringBuilder();
479

  
480
        if (!jdbcnsp.isValid()) {
481
            // TODO Exception
482
            throw new InitializeException(this.getProviderName(), new Exception(
483
                    "Parameters not valid"));
484
        }
485
        try {
486
            jdbcnsp.validate();
487
        } catch (ValidateDataParametersException e1) {
488
            throw new InitializeException(this.getProviderName(), e1);
489
        }
490

  
491
        FeatureType fType = jdbcnsp.getDefaultFeatureType();
492

  
493
        sql.append("Create table " + jdbcnsp.tableID()
494
                + "(");
495
        Iterator attrs = fType.iterator();
496
        String sqlAttr;
497
        List sqlAttrs = new ArrayList();
498

  
499
        while (attrs.hasNext()) {
500
            sqlAttr = helper
501
                    .getSqlFieldDescription((FeatureAttributeDescriptor) attrs
502
                            .next());
503
            if (sqlAttr != null) {
504
                sqlAttrs.add(sqlAttr);
505
            }
506
        }
507

  
508
        helper.stringJoin(sqlAttrs, ", ", sql);
509

  
510
        sql.append(")");
511

  
512
        final String sqlCreate = sql.toString();
513
        final List sqlAdditional = helper.getAdditionalSqlToCreate(ndsp, fType);
514

  
515
        List permissions = this.getHelper().createGrantStatements((JDBCNewStoreParameters) ndsp);
516
        if (permissions != null) {
517
            sqlAdditional.addAll(permissions);
518
        }
519

  
520
        if (((JDBCNewStoreParameters) ndsp).getPostCreatingStatement() != null) {
521
            sqlAdditional.add(((JDBCNewStoreParameters) ndsp).getPostCreatingStatement());
522
        }
523

  
524
        TransactionalAction action = new TransactionalAction() {
525

  
526
            public boolean continueTransactionAllowed() {
527
                // TODO Auto-generated method stub
528
                return false;
529
            }
530

  
531
            public Object action(Connection conn) throws DataException {
532
                Statement st = null;
533

  
534
                try {
535
                    st = conn.createStatement();
536
                } catch (SQLException e1) {
537
                    throw new JDBCSQLException(e1);
538
                }
539
                String sql = null;
540

  
541
                try {
542
                    sql = sqlCreate;
543
                    JDBCHelper.execute(st, sql);
544
                    if (sqlAdditional != null) {
545
                        Iterator iter = sqlAdditional.iterator();
546
                        while (iter.hasNext()) {
547
                            sql = (String) iter.next();
548
                            JDBCHelper.execute(st, sql);
549
                        }
550
                    }
551

  
552
                } catch (SQLException e) {
553
                    throw new JDBCExecuteSQLException(sql, e);
554
                } finally {
555
                    try {
556
                        st.close();
557
                    } catch (SQLException e) {
558
                        LOG.error("Exception clossing statement", e);
559
                    }
560
                    ;
561
                }
562

  
563
                return Boolean.TRUE;
564
            }
565

  
566
        };
567

  
568
        Boolean result = Boolean.FALSE;
569

  
570
        try {
571
            result = (Boolean) helper.doConnectionAction(action);
572
        } catch (DataException e) {
573
            throw e;
574
        } catch (Exception e) {
575
            // FIXME Exception
576
            throw new RuntimeException(e);
577
        }
578

  
579
        return result.booleanValue();
580
    }
581

  
582
    public List getDataStoreProviderNames() {
583
        List x = new ArrayList(1);
584
        x.add(JDBCStoreProvider.NAME);
585
        return x;
586
    }
587

  
588
    public void updateTableStatistics(String tableName) throws JDBCExecuteSQLException {
589

  
590
    }
591

  
592
    @Override
593
    public DataStoreParameters get(String name) throws DataException {
594
        JDBCStoreParameters params = this.createStoreParams();
595
        params.setTable(name);
596
        return params;
597
    }
598
}
1
package org.gvsig.fmap.dal.store.jdbc;
2

  
3
import java.util.List;
4
import org.gvsig.fmap.dal.DataServerExplorer;
5
import org.gvsig.fmap.dal.DataStore;
6
import org.gvsig.fmap.dal.DataStoreParameters;
7
import org.gvsig.fmap.dal.NewDataStoreParameters;
8
import org.gvsig.fmap.dal.exception.DataException;
9
import org.gvsig.fmap.dal.exception.RemoveException;
10
import org.gvsig.fmap.dal.feature.FeatureType;
11
import org.gvsig.fmap.dal.store.jdbc.exception.JDBCExecuteSQLException;
12

  
13
public interface JDBCServerExplorer extends DataServerExplorer {
14

  
15
    @Override
16
    boolean add(String providerName, NewDataStoreParameters ndsp, boolean overwrite) throws DataException;
17

  
18
    @Override
19
    boolean canAdd();
20

  
21
    @Override
22
    DataStoreParameters get(String name) throws DataException;
23

  
24
    NewDataStoreParameters getAddParameters() throws DataException;
25

  
26
    @Override
27
    List getDataStoreProviderNames();
28

  
29
    FeatureType getFeatureType(DataStoreParameters dsp) throws DataException;
30

  
31
    DataStoreParameters getOpenParameters() throws DataException;
32

  
33
    @Override
34
    String getProviderName();
35

  
36
    String getStoreName();
37

  
38
    @Override
39
    List list(int mode) throws DataException;
40

  
41
    DataStore open(DataStoreParameters dsp) throws DataException;
42

  
43
    @Override
44
    void remove(DataStoreParameters dsp) throws RemoveException;
45

  
46
    void updateTableStatistics(String database, String schema, String table) throws JDBCExecuteSQLException;
47
    
48
}
49

  

Also available in: Unified diff