/* * Copyright 2012 The Solmix Project * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software 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 * Lesser General Public License for more details. * * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * http://www.gnu.org/licenses/ * or see the FSF site: http://www.fsf.org. */ package org.solmix.fmk.call; import java.util.ArrayList; import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.solmix.api.call.DSCall; import org.solmix.api.call.DSCallCompleteCallback; import org.solmix.api.call.DSCallInterceptor; import org.solmix.api.call.DSCallInterceptor.Action; import org.solmix.api.datasource.DSRequest; import org.solmix.api.datasource.DSRequestData; import org.solmix.api.datasource.DSResponse; import org.solmix.api.datasource.DSResponse.Status; import org.solmix.api.datasource.DataSource; import org.solmix.api.exception.SlxException; import org.solmix.api.jaxb.Eoperation; import org.solmix.api.jaxb.TfieldNameValue; import org.solmix.api.jaxb.ToperationBinding; import org.solmix.api.serialize.JSParser; import org.solmix.api.serialize.JSParserFactory; import org.solmix.api.serialize.XMLParser; import org.solmix.api.serialize.XMLParserFactory; import org.solmix.api.types.Texception; import org.solmix.api.types.Tmodule; import org.solmix.api.types.TransactionPolicy; import org.solmix.commons.util.DataUtils; import org.solmix.ds.context.Context; import org.solmix.fmk.datasource.BasicDataSource; import org.solmix.fmk.datasource.DSResponseImpl; import org.solmix.fmk.datasource.DefaultDataSourceManager; import org.solmix.fmk.serialize.JSParserFactoryImpl; import org.solmix.fmk.serialize.XMLParserFactoryImpl; import org.solmix.fmk.velocity.Velocity; /** * complex relationship at this class,ant simple configuration at data class {@link org.solmix.api.data.DSCManagerData * DSCManagerData}. * * @author solmix.f@gmail.com * @since 0.0.1 * @version 110040 2011-1-1 solmix-ds */ public class DSCallImpl implements DSCall { private static final Logger log = LoggerFactory.getLogger(DSCallImpl.class.getName()); protected final DSCallInterceptor[] interceptors;// = new LinkedList<DSCallInterceptor>(); private List<DataSource> dsToFree; private final HashSet<DSCallCompleteCallback> callbacks = new HashSet<DSCallCompleteCallback>(); private List<DSRequest> requests; private Context context; private JSParser jsParser; private XMLParser xmlParser; private Map<Object, Object> attributes; public Map<String, Object> templateContext; private final Map<DSRequest, DSResponse> responseMap = new HashMap<DSRequest, DSResponse>();; private Long transactionNum; private TransactionPolicy transactionPolicy; private enum STATUS { INIT , BEGIN , SUCCESS , FAILED , END; } private STATUS status = STATUS.INIT; public DSCallImpl(DSCallInterceptor... callInterceptors) { this.interceptors = callInterceptors; } public DataSource getDataSource(String dsName) throws Exception { DataSource ds = DefaultDataSourceManager.getDataSource(dsName); if (ds != null) dsToFree.add(ds); return ds; } /** * @return the transactionPolicy */ @Override public TransactionPolicy getTransactionPolicy() { return transactionPolicy; } /** * @param transactionPolicy the transactionPolicy to set * @throws SlxException */ @Override public void setTransactionPolicy(TransactionPolicy transactionPolicy) throws SlxException { if (status == STATUS.BEGIN) throw new SlxException(Tmodule.DATASOURCE, Texception.DS_REQUEST_ALREADY_STARTED, "dsRequest already started."); this.transactionPolicy = transactionPolicy; } /** * @return the transactionNum */ @Override public Long getTransactionNum() { return transactionNum; } /** * @param transactionNum the transactionNum to set */ @Override public void setTransactionNum(Long transactionNum) { this.transactionNum = transactionNum; } /** * {@inheritDoc} * * @see org.solmix.api.call.DSCall#requestCount() */ @Override public int requestCount() { return requests.size(); } @Override public void applyEarlierResponseValues(DSRequest dsReq) throws SlxException { // setRequestProcessingStarted(true); DSRequestData _reqData = dsReq.getContext(); // Date _currentDate = new Date(); // addToTemplateContext("currentDate", _currentDate); // if (getFromTemplateContext("transactionDate") == null) // addToTemplateContext("transactionDate", _currentDate); DataSource ds = dsReq.getDataSource(); if (ds == null) return; Eoperation opType = _reqData.getOperationType(); String opId = _reqData.getOperationId(); ToperationBinding opBinding = ds.getContext().getOperationBinding(opType, opId); if (opBinding == null) return; List<TfieldNameValue> criterias = opBinding.getCriteria(); List<TfieldNameValue> values = opBinding.getValues(); if (criterias == null && values == null) return; Map<String, Object> params = Velocity.getStandardContextMap(dsReq); for (TfieldNameValue criteria : criterias) { String fieldName = criteria.getFieldName(); String value = criteria.getValue(); if (fieldName != null && value != null) { Object evaluation = Velocity.evaluate(value, params); dsReq.getContext().addToCriteria(fieldName, evaluation); } } for (TfieldNameValue v : values) { String fieldName = v.getFieldName(); String value = v.getValue(); if (fieldName != null && value != null) { Object evaluation = Velocity.evaluate(value, params); dsReq.getContext().addToCriteria(fieldName, evaluation); } } } /** * {@inheritDoc} * * @see org.solmix.api.call.DSCall#run() */ @Override public void run() throws SlxException { for (DSCallInterceptor i : interceptors) { i.prepareRequest(this, context); } for (DSCallInterceptor i : interceptors) { i.inspect(this, context); } List<DSRequest> reqs = getRequests(); boolean _failure = false; status = STATUS.BEGIN; if (log.isTraceEnabled()) log.trace("Performing " +requestCount() + " operation(s) "); try { for (DSRequest req : reqs) { DSResponse res=null; try { res = req.execute(); } catch (SlxException e) { res = new DSResponseImpl(req); res.setRawData(e.getMessage()); res.setStatus(Status.STATUS_FAILURE); log.error("DSRequest execute Failed:",e); } if (res != null) responseMap.put(req, res); } if (responseMap.size() != requestCount()) { throw new SlxException(Tmodule.DSC, Texception.TRANSACTION_EXCEPTION, new StringBuilder().append("Having ").append(requestCount()).append(" requests,But having ").append(responseMap.size()).append( " responses").toString()); } for (DSRequest req : reqs) { DSResponse res = getResponse(req); if (res.getStatus().value() < 0) { _failure = true; break; } } try { if (_failure) onFailure(); else onSuccess(); } catch (Exception e) { log.warn(DataUtils.getStackTrace(e)); } // post inspect for (DSCallInterceptor i : interceptors) { Action res = i.postInspect(this, context); if (res == Action.CANCELLED) break; } } finally { /** * loop requests to free resource.because some request support transaction,so it's datasource is not free no * execute ,but now a DSC session is complete,the DataSource borrow from PoolManager must return . */ for (Object request : requests) { // process DSRequest if (request instanceof DSRequest) { DSRequest dsRequest = (DSRequest) request; if (!dsRequest.isFreeOnExecute()) dsRequest.freeResources(); } } } } /** * Add requst to a request list,principally for mulitply request * * @param req */ @Override public void addRequest(DSRequest req) { if (requests == null) requests = new ArrayList<DSRequest>(); requests.add(req); } @Override public List<DSRequest> getRequests() { return requests; } @Override public DSResponse getResponse(DSRequest req) { return this.responseMap.get(req); } @Override public Context getRequestContext() { return context; } @Override public void setRequestContext(Context context) throws SlxException { this.context = context; } /** * {@inheritDoc} * * @see org.solmix.api.call.DSCall#freeDataSources() */ @Override public void freeDataSources() { if (dsToFree != null) for (DataSource ds : dsToFree) DefaultDataSourceManager.freeDataSource(ds); } /** * {@inheritDoc} * * @see org.solmix.api.call.DSCall#registerCallback(org.solmix.api.call.DSCallCompleteCallback) */ @Override public void registerCallback(DSCallCompleteCallback callback) { if (!callbacks.contains(callback)) callbacks.add(callback); } public DSResponse execute(final XAOp op) throws SlxException { op.setRpc(this); DSResponse t = null; try { t = op.exe(); } catch (SlxException e) { try { transactionFailed(op.getRequest(), t); } catch (Exception e1) { throw new SlxException(Tmodule.DSC, Texception.TRANSACTION_ROLLBACK_FAILTURE, "transaction rollback failure with rollback Exception:" + e1.getMessage() + " with Root Exception:" + e.getFullMessage()); } throw e; } boolean _transactionFailure = isXAFailure(op.getRequest(), t); if (_transactionFailure) { transactionFailed(op.getRequest(), t); throw new SlxException(Tmodule.DSC, Texception.TRANSACTION_BREAKEN, "transaction breaken because of one request failure."); } return t; } private boolean isXAFailure(DSRequest req, DSResponse res) throws SlxException { boolean _transactionFailure = false; if (res != null && res.getStatus().value() < 0) if (req.isRequestStarted()) { if (req.isJoinTransaction()) _transactionFailure = true; } else { BasicDataSource ds = (BasicDataSource) req.getDataSource(); if (ds.shouldAutoJoinTransaction(req) && (ds.shouldAutoStartTransaction(req, true) || requestQueueIncludesUpdates(req))) _transactionFailure = true; } return _transactionFailure; } /* * @Override public void send(DSRequest dsRequest, DSResponse dsResponse) throws SlxException { if * (data.getIsDownload() == null) data.setIsDownload(dsRequest.getContext().getIsDownload()); if (data.getIsExport() * == null) data.setIsExport(dsRequest.getContext().getIsExport()); // if (data.getIsExport() == null) { // * ToperationBinding __op = DataTools.getOperationBindingFromDSByRequest(dsRequest.getDataSource(), dsRequest); // * data.setIsExport(__op == null ? null : __op.isExportResults()); // } if(status!=STATUS.BEGIN){ throw new * SlxException * (Tmodule.DSC,Texception.TRANSACTION_MUST_END_BEFORE_SEND,"Transaction must end before send a DSRequest"); } * responseMap.put(dsRequest, dsResponse); if (responseMap.size() == requestCount()) { completeResponse(); } } */ @Override public boolean requestQueueIncludesUpdates(DSRequest req) throws SlxException { if (requests == null) return false; for (DSRequest request : requests) { if (request.equals(req)) { return false; } if (request.isModificationRequest()) return true; } return false; } public void beginTransaction() throws SlxException { if (status != STATUS.INIT) throw new SlxException(Tmodule.DSC, Texception.TRANSACTION_EXCEPTION, "Transaction have been started"); status = STATUS.BEGIN; } public void endTransaction() { status = STATUS.SUCCESS; try { onSuccess(); } catch (Exception e) { log.debug("exception when end transaction", e); } } public void rollback() throws SlxException { status = STATUS.FAILED; if (callbacks != null) for (DSCallCompleteCallback callback : callbacks) { callback.onFailure(this, true); } } protected void onSuccess() throws Exception { status = STATUS.SUCCESS; for (DSCallCompleteCallback callback : callbacks) { callback.onSuccess(this); } } protected void onFailure() throws Exception { status = STATUS.FAILED; boolean _transactionFailure = false; if (requests == null) return; for (DSRequest req : requests) { DSResponse resp = getResponse(req); _transactionFailure = isXAFailure(req, resp); } if (_transactionFailure) { for (DSRequest req : requests) { if (req.isJoinTransaction()) { DSResponse resp = getResponse(req); if (resp != null && resp.getStatus() == DSResponse.Status.STATUS_SUCCESS) resp.setStatus(DSResponse.Status.STATUS_TRANSACTION_FAILED); } } } if (callbacks != null) for (DSCallCompleteCallback callback : callbacks) { callback.onFailure(this, _transactionFailure); } } public DSResponse transactionExecute(DSRequest request) throws SlxException { if (status != STATUS.BEGIN) { throw new SlxException(Tmodule.DSC, Texception.TRANSACTION_NOT_STARTED, "Transaction not started ,you should call method startTransaction()"); } request.setDSCall(this); request.setCanJoinTransaction(true); DSResponse res = null; try { res = request.execute(); } catch (SlxException e) { try { transactionFailed(request, res); } catch (Exception e1) { throw new SlxException(Tmodule.DSC, Texception.TRANSACTION_ROLLBACK_FAILTURE, "transaction rollback failure with rollback Exception:" + e1.getMessage() + " with Root Exception:" + e.getFullMessage()); } throw e; } boolean _transactionFailure = isXAFailure(request, res); if (_transactionFailure) { transactionFailed(request, res); throw new SlxException(Tmodule.DSC, Texception.TRANSACTION_BREAKEN, "transaction breaken because of one request failure."); } return res; } private void transactionFailed(DSRequest request, DSResponse resp) throws SlxException { if (request.isJoinTransaction()) { if (resp != null && resp.getStatus() == DSResponse.Status.STATUS_SUCCESS) resp.setStatus(DSResponse.Status.STATUS_TRANSACTION_FAILED); } rollback(); } /** * @return the jsParser */ @Override public JSParser getJSParser() { if (jsParser == null) { JSParserFactory jsFactory = JSParserFactoryImpl.getInstance(); jsParser = jsFactory.get(); } return jsParser; } @Override public XMLParser getXMLParser() { if (xmlParser == null) { XMLParserFactory factory = XMLParserFactoryImpl.getInstance(); xmlParser = factory.get(); } return xmlParser; } @Override public Object getAttribute(Object key) { if (attributes != null) return attributes.get(key); return null; } @Override public void setAttribute(Object key, Object value) { if (attributes == null) attributes = new LinkedHashMap<Object, Object>(); attributes.put(key, value); } @Override public void removeAttribute(Object key) { if (attributes != null) attributes.remove(key); } @Override public Map<String, Object> getTemplateContext() { if(templateContext==null) templateContext=Collections.emptyMap(); return templateContext; } }