Project

General

Profile

Download (11.5 KB) Statistics
| Branch: | Tag: | Revision:
1
package eu.etaxonomy.vaadin.mvp;
2

    
3
import java.io.Serializable;
4

    
5
import org.apache.log4j.Logger;
6
import org.hibernate.Session;
7
import org.hibernate.engine.spi.SessionImplementor;
8
import org.springframework.beans.factory.annotation.Autowired;
9
import org.springframework.beans.factory.annotation.Qualifier;
10
import org.springframework.security.core.context.SecurityContext;
11
import org.springframework.security.core.context.SecurityContextHolder;
12
import org.springframework.transaction.IllegalTransactionStateException;
13
import org.springframework.transaction.TransactionDefinition;
14
import org.springframework.transaction.support.DefaultTransactionDefinition;
15

    
16
import com.vaadin.server.ServletPortletHelper;
17
import com.vaadin.server.VaadinRequest;
18
import com.vaadin.server.VaadinService;
19
import com.vaadin.server.VaadinSession;
20
import com.vaadin.ui.UI;
21

    
22
import eu.etaxonomy.cdm.api.application.CdmRepository;
23
import eu.etaxonomy.cdm.vaadin.server.CdmSpringVaadinServletService;
24
import eu.etaxonomy.cdm.vaadin.server.RequestStartListener;
25
import eu.etaxonomy.cdm.vaadin.session.IntraViewConversationDirector;
26
import eu.etaxonomy.cdm.vaadin.session.ViewScopeConversationHolder;
27
import eu.etaxonomy.vaadin.ui.navigation.NavigationManager;
28
import eu.etaxonomy.vaadin.ui.navigation.NavigationManagerBean;
29

    
30
/**
31
 * AbstractPresenter is the base class of all presenter components. Presenter's
32
 * role is to govern the view and control the complex UI logic based on
33
 * notifications presenter receives from its view.
34
 *
35
 * @author Peter / Vaadin
36
 *
37
 * @param <V>
38
 *            type of the view this presenter governs
39
 */
40
public abstract class AbstractPresenter<V extends ApplicationView> implements Serializable, IntraViewConversationDirector, RequestStartListener {
41

    
42

    
43
    private static final long serialVersionUID = 5260910510283481832L;
44

    
45
    public static final Logger logger = Logger.getLogger(AbstractPresenter.class);
46

    
47
	private V view;
48

    
49

    
50
	protected V getView() {
51
	    if(view == null){
52
            Logger.getLogger(this.getClass()).warn("CDM-VAADIN#6562: presenter " + toString() + " without view.");
53
        }
54
		return view;
55
	}
56

    
57
	@Autowired
58
	@Qualifier("cdmRepository")
59
	private CdmRepository repo;
60

    
61
	@Autowired
62
	private NavigationManager navigationManager;
63

    
64
	private ViewScopeConversationHolder conversationHolder;
65

    
66
	protected DefaultTransactionDefinition definition = null;
67

    
68
    protected boolean conversationBound;
69

    
70
	@Autowired
71
	private void setConversationHolder(ViewScopeConversationHolder conversationHolder){
72
	    this.conversationHolder = conversationHolder;
73
	    this.conversationHolder.setDefinition(getTransactionDefinition());
74
	}
75

    
76
	protected TransactionDefinition getTransactionDefinition(){
77
	    if(definition == null){
78
    	    definition = new DefaultTransactionDefinition();
79
    	    definition.setReadOnly(true);
80
	    }
81
	    return definition;
82
	}
83

    
84

    
85

    
86

    
87
	/**
88
	 * @return the repo
89
	 */
90
	public CdmRepository getRepo() {
91
	    if(!conversationBound){
92
	        // this is the central access point for getting access to the service layer.
93
	        // In case the presenter needs access to the repository, it most probably will use
94
	        // a service, so it is a good idea to bind the conversation at this point.
95
	        bindConversation();
96
	    }
97
	    return repo;
98
	}
99

    
100
	/**
101
     * @return
102
     *
103
     * FIXME is it ok to use the SecurityContextHolder or do we need to hold the context in the vaadin session?
104
     */
105
    protected SecurityContext currentSecurityContext() {
106
        return SecurityContextHolder.getContext();
107
    }
108

    
109
    /**
110
     * @return
111
     */
112
    protected Session getSession() {
113
        Session session = conversationHolder.getSession();
114
        logger.trace(this._toString() + ".getSession() - session:" + session.hashCode() +", persistenceContext: " + ((SessionImplementor)session).getPersistenceContext() + " - " + session.toString());
115
        return session;
116
    }
117

    
118
    protected String _toString(){
119
        return this.getClass().getSimpleName() + "@" + this.hashCode();
120
    }
121

    
122
	/**
123
	 * Notifies the presenter that its view is initialized so that presenter can
124
	 * start its own initialization if required.
125
	 *
126
	 * @param view
127
	 */
128
	protected final void init(V view) {
129
	    logger.trace(String.format("Presenter %s init()", _toString()));
130
		this.view = view;
131
		// bind the conversation to the thread of the first request send to the according View
132
		// all other requests are handled in onRequestStart()
133
		// logger.trace(String.format(">>>>> %s init() bind()", _toString()));
134
	    ensureBoundConversation();
135
	    // register as request start and end listener
136
	    VaadinService service = UI.getCurrent().getSession().getService();
137
	    if(service instanceof CdmSpringVaadinServletService){
138
	        logger.trace(String.format("~~~~~ %s register as request listener", _toString()));
139
	        ((CdmSpringVaadinServletService)service).addRequestEndListener(this);
140
	        if(logger.isTraceEnabled()){
141
	            ((CdmSpringVaadinServletService)service).addRequestStartListener(this);
142
	        }
143
	    } else {
144
	        throw new RuntimeException("Using the CdmSpringVaadinServletService is required for proper per view conversation handling");
145
	    }
146
		onPresenterReady();
147
	}
148

    
149
	/**
150
	 * Returns <code>true</code> for:
151
	 * <ul>
152
	 *   <li>..</li>
153
	 * <ul>
154
	 *
155
	 * Return <code>false</code> for:
156
	 *
157
	 * <ul>
158
     *   <li>UILD request in a existing view, like clicking on a button</li>
159
     * <ul>
160
     *
161
	 * @return
162
	protected boolean isActiveView(){
163
        return UI.getCurrent() != null && getView() != null && getView() == navigationManager.getCurrentView();
164
    }
165
	 */
166

    
167
    /**
168
     *
169
     */
170
	protected void bindConversation() {
171
        logger.trace(String.format(">>>>> %s bind()", _toString()));
172
        conversationHolder.bind();
173
        conversationBound = true;
174
    }
175

    
176
    @Override
177
    public void ensureBoundConversation() {
178
        if(!conversationBound){
179
            bindConversation();
180
        }
181
        if(!conversationHolder.isTransactionActive()){
182
            logger.trace(String.format(">>   %s starting transaction ", _toString()));
183
            conversationHolder.startTransaction();
184
        }
185
    }
186

    
187
    /**
188
     *
189
     */
190
    protected void unbindConversation() {
191
        logger.trace(String.format("<<<<< %s unbind()", _toString()));
192
        conversationHolder.unbind();
193
        // FIXME conversationHolder.isTransactionActive() always returns true
194
        // see https://dev.e-taxonomy.eu/redmine/issues/6780
195
        if(false && conversationHolder.isTransactionActive()){
196
            logger.trace(String.format("<<    %s comitting transaction ", _toString()));
197
            try{
198
                conversationHolder.commit(false);
199
            } catch (IllegalTransactionStateException | IllegalStateException e){
200
                // log this exception, but stop from propagating
201
                // FIXME remove this catch once https://dev.e-taxonomy.eu/redmine/issues/6780 is fixed
202
                logger.error(e.getMessage());
203
            }
204
        }
205
        conversationBound = false;
206
    }
207

    
208
    /**
209
	 * Extending classes should overwrite this method in order to perform logic
210
	 * after presenter has finished initializing.
211
	 *
212
	 * At this point in the life cycle of the MVP the {@link AbstractView#initContent() initContent()}
213
	 * method has been executed already.
214
	 */
215
	protected void onPresenterReady() {
216
	    logger.trace(String.format("Presenter %s ready", _toString()));
217
	}
218

    
219
    /**
220
     * <b>ONLY USED FOR LOGGING</b> when Level==TRACE
221
     * {@inheritDoc}
222
     */
223
    @Override
224
    public void onRequestStart(VaadinRequest request){
225

    
226
        if( ! requestNeedsConversation(request) ){
227
            // ignore hartbeat, fileupload, push etc
228
            logger.trace("ignoring request:" + request.getPathInfo());
229
            return;
230
        }
231
        logger.trace("onRequestStart() " + request.getPathInfo() + " " + _toString());
232
    }
233

    
234
    /**
235
     * @param request
236
     * @return
237
     */
238
    protected boolean requestNeedsConversation(VaadinRequest request) {
239
        return !(
240
                ServletPortletHelper.isAppRequest(request) // includes published file request
241
             || ServletPortletHelper.isFileUploadRequest(request)
242
             || ServletPortletHelper.isHeartbeatRequest(request)
243
             || ServletPortletHelper.isPushRequest(request)
244
             );
245
    }
246

    
247
    @Override
248
    public void onRequestEnd(VaadinRequest request, VaadinSession session){
249

    
250
        if( ! requestNeedsConversation(request) ){
251
            // ignore hartbeat, fileupload, push etc
252
            logger.trace("ignoring request:" + request.getPathInfo());
253
            return;
254
        }
255

    
256
        // always unbind at the end of a request to clean up the threadLocal variables in the
257
        // TransactionManager. This is crucial since applications containers manage threads in a pool
258
        // and the recycled threads may still have a reference to a SessionHolder from the processing
259
        // of a former request
260
        logger.trace("onRequestEnd() " + request.getPathInfo() + " " + _toString());
261
        if(conversationBound){
262
            unbindConversation();
263
        }
264
    }
265

    
266
    public final void onViewEnter() {
267
	    logger.trace(String.format("%s onViewEnter()", _toString()));
268
	    handleViewEntered();
269
	}
270

    
271
	public final void onViewExit() {
272
	    logger.trace(String.format("%s onViewExit()", _toString()));
273
	    handleViewExit();
274
	    // un-register as request start and end listener
275
	    if(conversationBound){
276
    	    logger.trace(String.format("<<<<< %s onViewExit() unbind()", _toString()));
277
            conversationHolder.unbind();
278
            conversationBound = false;
279
	    }
280
	    logger.trace(String.format("<<<<< %s onViewExit() close()", _toString()));
281
	    conversationHolder.close();
282
        VaadinService service = UI.getCurrent().getSession().getService();
283
        if(service instanceof CdmSpringVaadinServletService){
284
            logger.trace(String.format("~~~~~ %s un-register as request listener", _toString()));
285
            ((CdmSpringVaadinServletService)service).removeRequestEndListener(this);
286
            if(logger.isTraceEnabled()){
287
                ((CdmSpringVaadinServletService)service).removeRequestStartListener(this);
288
            }
289
        } else {
290
            throw new RuntimeException("Using the CdmSpringVaadinServletService is required for proper per view conversation handling");
291
        }
292
	}
293

    
294
	/**
295
	 * Extending classes should overwrite this method to react to the event when
296
	 * user has navigated into the view that this presenter governs.
297
	 * For implementations of {@link AbstractPopupEditor AbstractPopupEditors} this is usually
298
	 * called before the data item has been bound. This order is guaranteed since popup editors
299
	 * are managed through the {@link NavigationManagerBean}
300
	 */
301
	public void handleViewEntered() {
302
	}
303

    
304
    /**
305
     * Extending classes may overwrite this method to react to
306
     * the event when user leaves the view that this presenter
307
     * governs. This method is executed before un-binding and closing the
308
     * conversation holder.
309
     */
310
    public void handleViewExit() {
311
    }
312

    
313
    /**
314
     * @return the navigationManager
315
     */
316
    public NavigationManager getNavigationManager() {
317
        return navigationManager;
318
    }
319

    
320
    protected ViewScopeConversationHolder getConversationHolder(){
321
        return conversationHolder;
322
    }
323

    
324
    /**
325
     * @param repo the repo to set
326
     */
327
    protected void setRepo(CdmRepository repo) {
328
        this.repo = repo;
329
    }
330

    
331
    /**
332
     * @param navigationManager the navigationManager to set
333
     */
334
    protected void setNavigationManager(NavigationManager navigationManager) {
335
        this.navigationManager = navigationManager;
336
    }
337

    
338

    
339

    
340
}
(6-6/8)