001/*
002 * (C) Copyright 2014, 2016 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache 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.apache.org/licenses/LICENSE-2.0
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 *
016 */
017package org.nuxeo.runtime.datasource;
018
019import java.sql.SQLException;
020import java.util.HashMap;
021import java.util.HashSet;
022import java.util.Map;
023import java.util.Set;
024
025import org.apache.commons.logging.LogFactory;
026import org.nuxeo.common.xmap.annotation.XNode;
027import org.nuxeo.common.xmap.annotation.XNodeList;
028import org.nuxeo.common.xmap.annotation.XObject;
029import org.nuxeo.runtime.model.ContributionFragmentRegistry;
030import org.tranql.connector.ExceptionSorter;
031
032/**
033 *
034 *
035 * @since 8.3
036 */
037public class DatasourceExceptionSorter implements ExceptionSorter {
038
039    public enum Classcode {
040        NoError("00"),
041        Warning("01"),
042        NoData("02"),
043        DynamicSQLError("07"),
044        ConnectionException("08"),
045        TriggeredActionException("09"),
046        FeatureNotSupported("0A"),
047        InvalidTransactionInitiation("0B"),
048        InvalidTargetTypeSpecification("0D"),
049        InvalidSchemaNameListSpecification("0E"),
050        LocatorException("0F"),
051        ResignalWhenHandlerNotActive("0K"),
052        InvalidGrantor("0L"),
053        InvalidSqlInvokedProcedureReference("0M"),
054        MappingError("0N"),
055        InvalidRoleSpecification("0P"),
056        InvalidTransformGroupNameSpecification("0S"),
057        TargetTableDisagreesWithCursorSpecification("0T"),
058        AttemptToAssignNonUpdatableColumn("0U"),
059        AttemptToAssignToOrderingColumn("0V"),
060        ProhibitedStatementEncouteredDuringTriggerExecution("0W"),
061        DiagnosticsException("0Z"),
062        XQuery("10"),
063        CaseNotFoundInCaseStatement("20"),
064        CardinalityViolation("21"),
065        DataException("22"),
066        IntegrityConstraintViolation("23"),
067        InvalidCursorState("24"),
068        InvalidTransactionState("25"),
069        InvalidSQLStatementName("26"),
070        TriggeredDataChangeViolation("27"),
071        InvalidAuthorizationSpeciciation("28"),
072        DependentPrivilegeDescriptorsAlreadyExsist("2B"),
073        InvalidConnectionName("2E"),
074        InvalidCharacterSetName("2C"),
075        InvalidTransactionTermination("2D"),
076        SqlRoutineException("2F"),
077        InvalidSessionCollationSpecication("2H"),
078        InvalidSqlStatementIdentifier("30"),
079        InvalidSqlDescriptorName("33"),
080        InvalidCursorName("34"),
081        InvalidConditionNumber("35"),
082        CursorSensivityException("36"),
083        SyntaxError("37"),
084        ExternalRoutineException("38"),
085        ExternalRoutineInvocationException("39"),
086        SavepointException("3B"),
087        InvalidCatalogName("3D"),
088        AmbiguousCursorName("3C"),
089        InvalidSchemaName("3F"),
090        TransactionRollback("40"),
091        SyntaxErrorOrAccessRuleViolation("42"),
092        WithCheckOptionViolation("44"),
093        JavaErrors("46"),
094        InvalidApplicationState("51"),
095        InvalidOperandOrInconsistentSpecification("53"),
096        SqlOrProductLimitExcedeed("54"),
097        ObjectNotInPrerequisiteState("55"),
098        MiscellaneoudSqlOrProductError("56"),
099        ResourceNotAvailableOrOperatorIntervention("57"),
100        SystemError("58"),
101        CommonUtilitiesAndTools("5U"),
102        RemoteDatabaseAccess("HZ");
103
104        private String value;
105
106        Classcode(String code) {
107            value = code;
108        }
109
110    }
111
112    @XObject("sorter")
113    public static class Configuration {
114
115        @XNode("@id")
116        String id = "";
117
118        @XNode("@override")
119        boolean override = false;
120
121        @XNode("@path")
122        String pathname;
123
124        boolean matches(String classname) {
125            return classname.startsWith(pathname);
126        }
127
128        @XNodeList(value = "code", type = String[].class, componentType = String.class)
129        public void setCodes(String... values) {
130            for (String value : values) {
131                Classcode classcode = Classcode.valueOf(value);
132                if (classcode != null) {
133                    value = classcode.value;
134                }
135                int length = value.length();
136                if (length == 2) {
137                    codes.add(value);
138                } else if (length == 5) {
139                    states.add(value);
140                } else {
141                    LogFactory.getLog(DatasourceExceptionSorter.class).error("invalid code " + value);
142                }
143            }
144        }
145
146        final Set<String> codes = new HashSet<>();
147
148        final Set<String> states = new HashSet<>();
149
150        @XNodeList(value = "vendor", type = HashSet.class, componentType = Integer.class)
151        Set<Integer> vendors = new HashSet<>();
152
153        boolean isFatal(String sqlstate, Integer vendor) {
154            String code = sqlstate.substring(0, 2);
155            return codes.contains(code) || states.contains(sqlstate) || vendors.contains(vendor);
156        }
157    }
158
159    public static class Registry extends ContributionFragmentRegistry<Configuration> {
160
161        final Map<String, Configuration> actuals = new HashMap<>();
162
163        @Override
164        public String getContributionId(Configuration contrib) {
165            return contrib.id;
166        }
167
168        @Override
169        public void contributionUpdated(String id, Configuration contrib, Configuration newOrigContrib) {
170            actuals.put(id, contrib);
171        }
172
173        @Override
174        public void contributionRemoved(String id, Configuration origContrib) {
175            actuals.put(id, origContrib);
176        }
177
178        @Override
179        public Configuration clone(Configuration orig) {
180            Configuration cloned = new Configuration();
181            cloned.states.addAll(orig.states);
182            cloned.codes.addAll(orig.codes);
183            return cloned;
184        }
185
186        @Override
187        public void merge(Configuration src, Configuration dst) {
188            if (src.override) {
189                dst.states.clear();
190                dst.codes.clear();
191            }
192            dst.states.addAll(src.states);
193            dst.codes.addAll(src.codes);
194        }
195
196        public Configuration lookup(SQLException se) {
197            for (StackTraceElement frame : se.getStackTrace()) {
198                for (Configuration config : actuals.values()) {
199                    if ("".equals(config.id)) {
200                        continue;
201                    }
202                    if (config.matches(frame.getClassName())) {
203                        return config;
204                    }
205                }
206            }
207            return actuals.get("");
208        }
209
210    }
211
212    Configuration configuration;
213
214    @Override
215    public boolean isExceptionFatal(Exception e) {
216        if (!(e instanceof SQLException)) {
217            return true;
218        }
219        SQLException se = (SQLException) e;
220        String statuscode = se.getSQLState();
221        Integer errorcode = Integer.valueOf(se.getErrorCode());
222        if (configuration == null) {
223            configuration = DataSourceComponent.instance.sorterRegistry.lookup(se);
224        }
225        return configuration.isFatal(statuscode, errorcode);
226    }
227
228    @Override
229    public boolean rollbackOnFatalException() {
230        return true;
231    }
232
233}