001/**
002 * Copyright 2005-2016 The Kuali Foundation
003 *
004 * Licensed under the Educational Community License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 * http://www.opensource.org/licenses/ecl2.php
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 */
016package org.kuali.rice.core.framework.persistence.platform;
017
018import org.apache.ojb.broker.PersistenceBroker;
019import org.apache.ojb.broker.accesslayer.LookupException;
020import org.kuali.rice.core.api.config.property.ConfigContext;
021import org.kuali.rice.core.api.util.RiceConstants;
022
023import java.sql.Connection;
024import java.sql.PreparedStatement;
025import java.sql.ResultSet;
026import java.sql.SQLException;
027import java.util.regex.Pattern;
028
029/**
030 * DatabasePlatform implementation that generates Oracle-compliant SQL
031 * @author Kuali Rice Team (rice.collab@kuali.org)
032 */
033public class OracleDatabasePlatform extends ANSISqlDatabasePlatform {
034
035        private static final org.apache.log4j.Logger LOG = org.apache.log4j.Logger.getLogger(OracleDatabasePlatform.class);
036        private static final long DEFAULT_TIMEOUT_SECONDS = 60 * 60; // default to 1 hour
037        public static final long WAIT_FOREVER = -1;
038        
039        private static final Pattern APOS_PAT = Pattern.compile("'");
040
041    @Override
042    public String applyLimitSql(Integer limit) {
043        if (limit != null) {
044            return "rownum <= " + limit.intValue();
045        }
046        return null;
047    }  
048    
049    public String getStrToDateFunction() {
050        return "TO_DATE";
051    }
052    
053    public String getCurTimeFunction() {
054        return "sysdate";
055    }
056    
057    public String getDateFormatString(String dateFormatString) {
058        return "'" + dateFormatString + "'";
059    }
060
061    /**
062     * Generate next id value for the logical sequence given the JDBC Connection
063     * @param sequenceName the logical sequence name
064     * @param connection JDBC Connection to use (without closing)
065     * @return next id in sequence or RuntimeException on error
066     */
067    @Override
068    protected Long getNextValSqlJdbc(String sequenceName, Connection connection) {
069        PreparedStatement statement = null;
070        ResultSet resultSet = null;
071        try {
072            statement = connection.prepareStatement("select " + sequenceName + ".nextval from dual");
073            resultSet = statement.executeQuery();
074
075            if (!resultSet.next()) {
076                throw new RuntimeException("Error retrieving next option id for action list from sequence.");
077            }
078            return new Long(resultSet.getLong(1));
079        } catch (SQLException e) {
080            throw new RuntimeException("Error retrieving next option id for action list from sequence.", e);
081        } finally {
082            if (statement != null) {
083                try {
084                    statement.close();
085                } catch (SQLException e) {
086                }
087            }
088            if (resultSet != null) {
089                try {
090                    resultSet.close();
091                } catch (SQLException e) {
092                }
093            }
094        }
095    }
096    
097    @Deprecated
098    @Override
099        protected Long getNextValSqlOjb(String sequenceName, PersistenceBroker persistenceBroker) {
100                try {
101                        Connection connection = persistenceBroker.serviceConnectionManager().getConnection();
102            return getNextValSqlJdbc(sequenceName, connection);
103                } catch (LookupException e) {
104                        throw new RuntimeException("Error retrieving next option id for action list from sequence.", e);
105                }
106        }
107
108    public String getLockRouteHeaderQuerySQL(String documentId, boolean wait) {
109        long timeoutSeconds = getTimeoutSeconds();
110        String waitClause = "";
111        if (!wait) {
112                waitClause = " NOWAIT";
113        } else if (wait && timeoutSeconds > 0) {
114                waitClause = " WAIT " + timeoutSeconds;
115        }
116        return "SELECT DOC_HDR_ID FROM KREW_DOC_HDR_T WHERE DOC_HDR_ID=? FOR UPDATE" + waitClause;
117    }
118
119    public String toString() {
120        return "[OracleDatabasePlatform]";
121    }
122
123    protected long getTimeoutSeconds() {
124        String timeoutValue = ConfigContext.getCurrentContextConfig().getDocumentLockTimeout();
125        if (timeoutValue != null) {
126                try {
127                        return Long.parseLong(timeoutValue);
128                } catch (NumberFormatException e) {
129                        LOG.warn("Failed to parse document lock timeout as it was not a valid number: " + timeoutValue);
130                }
131        }
132        return DEFAULT_TIMEOUT_SECONDS;
133    }
134    
135    public String getSelectForUpdateSuffix(long waitMillis) {
136        String sql = "for update";
137        if (WAIT_FOREVER == waitMillis) {
138            // do nothing
139            LOG.warn("Selecting for update and waiting forever...");
140        } else if (RiceConstants.NO_WAIT == waitMillis) {
141            sql += " nowait";
142        } else {
143            // Oracle only supports wait time in seconds...
144            long seconds = waitMillis / 1000;
145            if (seconds == 0) seconds = 1;
146            sql += " wait " + seconds;
147        }
148        return sql;
149    }
150    
151    /**
152     * Performs Oracle-specific escaping of String parameters.
153     * 
154     * @see DatabasePlatform#escapeString(java.lang.String)
155     */
156    public String escapeString(String sqlString) {
157        return (sqlString != null) ? APOS_PAT.matcher(sqlString).replaceAll("''") : null;
158    }
159
160    /**
161     * Converts date-only values into JDBC d{...} date literals, but converts date-and-time values into timestamp
162     * strings that are then converted into date values via the strToDateFunction.
163     * 
164     * @see ANSISqlDatabasePlatform#getDateSQL(java.lang.String, java.lang.String)
165     */
166        @Override
167        public String getDateSQL(String date, String time) {
168                String d = date.replace('/', '-');
169        if (time == null) {
170            return new StringBuilder("{d '").append(d).append("'}").toString();
171        } else {
172            return new StringBuilder(getStrToDateFunction()).append("('").append(d).append(" ").append(
173                        time).append("', 'YYYY-MM-DD HH24:MI:SS')").toString(); 
174        }
175        }
176}