/* * dCache - http://www.dcache.org/ * * Copyright (C) 2016 Deutsches Elektronen-Synchrotron * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as * published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.dcache.pinmanager; import org.springframework.dao.support.DataAccessUtils; import org.springframework.jdbc.JdbcUpdateAffectedIncorrectNumberOfRowsException; import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.support.JdbcDaoSupport; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import javax.annotation.Nullable; import javax.annotation.ParametersAreNonnullByDefault; import javax.security.auth.Subject; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Statement; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collection; import java.util.Date; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.stream.Stream; import diskCacheV111.util.PnfsId; import org.dcache.auth.Subjects; import org.dcache.pinmanager.model.Pin; import org.dcache.util.SqlGlob; import static java.util.Arrays.asList; import static java.util.stream.Collectors.joining; @ParametersAreNonnullByDefault public class JdbcDao extends JdbcDaoSupport implements PinDao { @Override public PinCriterion where() { return new JdbcPinCriterion(); } @Override public PinDao.PinUpdate set() { return new JdbcPinUpdate(); } @Override public List<Pin> get(PinCriterion criterion) { JdbcCriterion c = (JdbcCriterion) criterion; return getJdbcTemplate().query( "SELECT * FROM pins WHERE " + c.getPredicate(), c.getArgumentsAsArray(), (RowMapper<Pin>) this::toPin); } @Override public List<Pin> get(PinCriterion criterion, int limit) { JdbcCriterion c = (JdbcCriterion) criterion; return getJdbcTemplate().query( "SELECT * FROM pins WHERE " + c.getPredicate() + " LIMIT " + limit, c.getArgumentsAsArray(), (RowMapper<Pin>) this::toPin); } @Override public Pin get(UniquePinCriterion criterion) { JdbcCriterion c = (JdbcCriterion) criterion; return DataAccessUtils.singleResult( getJdbcTemplate().query( "SELECT * FROM pins WHERE " + c.getPredicate(), c.getArgumentsAsArray(), (RowMapper<Pin>) this::toPin)); } @Override public int count(PinCriterion criterion) { JdbcCriterion c = (JdbcCriterion) criterion; return getJdbcTemplate().queryForObject( "SELECT count(*) FROM pins WHERE " + c.getPredicate(), c.getArgumentsAsArray(), Integer.class); } @Override public int update(PinCriterion criterion, PinUpdate update) { JdbcCriterion c = (JdbcCriterion) criterion; JdbcUpdate u = (JdbcUpdate) update; Object[] arguments = Stream.concat(u.getArguments().stream(), c.getArguments().stream()).toArray(Object[]::new); return getJdbcTemplate().update("UPDATE pins SET " + u.getUpdate() + " WHERE " + c.getPredicate(), arguments); } @Override public Pin update(UniquePinCriterion criterion, PinDao.PinUpdate update) { JdbcCriterion c = (JdbcCriterion) criterion; JdbcUpdate u = (JdbcUpdate) update; Object[] arguments = Stream.concat(u.getArguments().stream(), c.getArguments().stream()).toArray(Object[]::new); String sql = "UPDATE pins SET " + u.getUpdate() + " WHERE " + c.getPredicate(); int n = getJdbcTemplate().update(sql, arguments); if (n == 0) { return null; } if (n > 1) { throw new JdbcUpdateAffectedIncorrectNumberOfRowsException(sql, 1, n); } return get(where().sameIdAs(criterion)); } @Override public int delete(PinCriterion criterion) { JdbcCriterion c = (JdbcCriterion) criterion; return getJdbcTemplate().update("DELETE FROM pins WHERE " + c.getPredicate(), c.getArgumentsAsArray()); } @Override public void foreach(PinCriterion criterion, InterruptibleConsumer<Pin> f) throws InterruptedException { JdbcCriterion c = (JdbcCriterion) criterion; InterruptedException exception = getJdbcTemplate().query( "SELECT * FROM pins WHERE " + c.getPredicate(), c.getArgumentsAsArray(), rs -> { try { while (rs.next()) { f.accept(toPin(rs)); } } catch (InterruptedException e) { return e; } return null; }); if (exception != null) { throw exception; } } private static class JdbcCriterion { final StringBuilder predicate = new StringBuilder(); final List<Object> arguments = new ArrayList<>(); protected void addClause(String clause, Object... arguments) { if (predicate.length() > 0) { predicate.append(" AND "); } predicate.append(clause); this.arguments.addAll(asList(arguments)); } protected void whereFieldMatches(String field, SqlGlob pattern) { if (pattern.isGlob()) { addClause(field + "LIKE ?", pattern.toSql()); } else { addClause(field + " = ?", pattern.toString()); } } public String getPredicate() { return predicate.length() == 0 ? "true" : predicate.toString(); } public List<Object> getArguments() { return arguments; } public Object[] getArgumentsAsArray() { return arguments.toArray(new Object[arguments.size()]); } } private static class JdbcPinCriterion extends JdbcCriterion implements UniquePinCriterion { private Long id; private PnfsId pnfsId; private String requestId; @Override public JdbcPinCriterion id(long id) { addClause("id = ?", id); this.id = id; return this; } @Override public JdbcPinCriterion pnfsId(PnfsId id) { addClause("pnfsid = ?", id.toString()); this.pnfsId = id; return this; } @Override public JdbcPinCriterion requestId(String requestId) { addClause("request_id = ?", requestId); this.requestId = requestId; return this; } @Override public JdbcPinCriterion expirationTimeBefore(Date date) { addClause("expires_at < ?", new Timestamp(date.getTime())); return this; } @Override public JdbcPinCriterion state(Pin.State state) { addClause("state = ?", state.toString()); return this; } @Override public JdbcPinCriterion stateIsNot(Pin.State state) { addClause("state <> ?", state.toString()); return this; } @Override public JdbcPinCriterion pool(String pool) { addClause("pool = ?", pool); return this; } @Override public JdbcPinCriterion sticky(String sticky) { addClause("sticky = ?", sticky); return this; } @Override public JdbcPinCriterion sameIdAs(UniquePinCriterion criterion) { JdbcPinCriterion c = (JdbcPinCriterion) criterion; if (c.id != null) { return id(c.id); } else { return pnfsId(c.pnfsId).requestId(c.requestId); } } } private static class JdbcUpdate { final Map<String,Object> updates = new LinkedHashMap<>(); protected void set(String column, @Nullable Object value) { updates.put(column, value); } public Map<String,Object> updates() { return updates; } public Collection<Object> getArguments() { return updates.values(); } public String getUpdate() { return updates.keySet().stream().map(s -> s + " = ?").collect(joining(",")); } public String getInsert() { return updates.keySet().stream().collect(joining(",", "(", ")")) + " VALUES " + updates.keySet().stream().map(a -> "?").collect(joining(",", "(", ")")); } public Object get(String column) { return updates.get(column); } } private static class JdbcPinUpdate extends JdbcUpdate implements PinUpdate { @Override public PinUpdate expirationTime(@Nullable Date expirationTime) { set("expires_at", (expirationTime == null) ? null : new Timestamp(expirationTime.getTime())); return this; } @Override public PinUpdate pool(@Nullable String pool) { set("pool", pool); return this; } @Override public PinUpdate requestId(@Nullable String requestId) { set("request_id", requestId); return this; } @Override public PinUpdate state(Pin.State state) { set("state" , state.toString()); return this; } @Override public PinUpdate sticky(@Nullable String sticky) { set("sticky", sticky); return this; } @Override public PinUpdate subject(Subject subject) { set("uid", Subjects.getUid(subject)); set("gid", Subjects.getPrimaryGid(subject)); return this; } @Override public PinUpdate pnfsId(PnfsId pnfsId) { set("pnfsid", pnfsId.toString()); return this; } } @Override public Pin create(PinUpdate update) { JdbcUpdate u = (JdbcUpdate) update; KeyHolder keyHolder = new GeneratedKeyHolder(); u.set("created_at", new Timestamp(System.currentTimeMillis())); getJdbcTemplate().update( con -> { PreparedStatement ps = con.prepareStatement( "INSERT INTO pins " + u.getInsert(), Statement.RETURN_GENERATED_KEYS); Collection<Object> arguments = u.getArguments(); int i = 1; for (Object argument : arguments) { ps.setObject(i++, argument); } return ps; }, keyHolder); u.set("id", keyHolder.getKeys().get("id")); return toPin(u); } private Pin toPin(JdbcUpdate update) { Timestamp createdAt = (Timestamp) update.get("created_at"); Timestamp expiresAt = (Timestamp) update.get("expires_at"); return new Pin((long) update.get("id"), new PnfsId((String) update.get("pnfsid")), (String) update.get("request_id"), new Date(createdAt.getTime()), (expiresAt == null) ? null : new Date(expiresAt.getTime()), (long) update.get("uid"), (long) update.get("gid"), Pin.State.valueOf((String) update.get("state")), (String) update.get("pool"), (String) update.get("sticky")); } private Pin toPin(ResultSet rs, int rownum) throws SQLException { return toPin(rs); } private Pin toPin(ResultSet rs) throws SQLException { Timestamp createdAt = rs.getTimestamp("created_at"); Timestamp expiresAt = rs.getTimestamp("expires_at"); return new Pin(rs.getLong("id"), new PnfsId(rs.getString("pnfsid")), rs.getString("request_id"), new Date(createdAt.getTime()), (expiresAt == null) ? null : new Date(expiresAt.getTime()), rs.getLong("uid"), rs.getLong("gid"), Pin.State.valueOf(rs.getString("state")), rs.getString("pool"), rs.getString("sticky")); } }