001/**
002 *  Licensed to the Apache Software Foundation (ASF) under one or more
003 *  contributor license agreements.  See the NOTICE file distributed with
004 *  this work for additional information regarding copyright ownership.
005 *  The ASF licenses this file to You under the Apache License, Version 2.0
006 *  (the "License"); you may not use this file except in compliance with
007 *  the License.  You may obtain a copy of the License at
008 *
009 *     http://www.apache.org/licenses/LICENSE-2.0
010 *
011 *  Unless required by applicable law or agreed to in writing, software
012 *  distributed under the License is distributed on an "AS IS" BASIS,
013 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014 *  See the License for the specific language governing permissions and
015 *  limitations under the License.
016 */
017package org.apache.isis.core.metamodel.specloader;
018
019import java.lang.reflect.Method;
020import java.util.List;
021import java.util.Map;
022
023import javax.annotation.PostConstruct;
024import javax.annotation.PreDestroy;
025
026import com.google.common.collect.Maps;
027
028import org.slf4j.Logger;
029import org.slf4j.LoggerFactory;
030
031import org.apache.isis.applib.DomainObjectContainer;
032import org.apache.isis.core.commons.config.IsisConfiguration;
033import org.apache.isis.core.commons.lang.MethodExtensions;
034
035public class ServiceInitializer {
036
037    private final static Logger LOG = LoggerFactory.getLogger(ServiceInitializer.class);
038
039    private Map<String, String> props;
040    
041    static class ServiceInitMethods {
042        Object service;
043        Method postConstruct;
044        Method preDestrory;
045    }
046    
047    private Map<Object, Method> postConstructMethodsByService = Maps.newLinkedHashMap(); 
048    private Map<Object, Method> preDestroyMethodsByService = Maps.newLinkedHashMap(); 
049
050    // //////////////////////////////////////
051
052    public void validate(final IsisConfiguration configuration, DomainObjectContainer container, final List<Object> services) {
053        
054        this.props = configuration.asMap();
055        
056        for (final Object service : services) {
057            LOG.debug("checking for @PostConstruct and @PostDestroy methods on " + service.getClass().getName());
058            final Method[] methods = service.getClass().getMethods();
059
060            // @PostConstruct
061            for (final Method method : methods) {
062                
063                final PostConstruct postConstructAnnotation = method.getAnnotation(PostConstruct.class);
064                if(postConstructAnnotation == null) {
065                    continue;
066                }
067                final Method existing = postConstructMethodsByService.get(service);
068                if(existing != null) {
069                    throw new RuntimeException("Found more than one @PostConstruct method; service is: " + service.getClass().getName() + ", found " + existing.getName() + " and " + method.getName());
070                }
071                
072                final Class<?>[] parameterTypes = method.getParameterTypes();
073                switch(parameterTypes.length) {
074                case 0:
075                    break;
076                case 1:
077                    if(Map.class != parameterTypes[0]) {
078                        throw new RuntimeException("@PostConstruct method must be no-arg or 1-arg accepting java.util.Map; method is: " + service.getClass().getName() + "#" + method.getName());
079                    }
080                    break;
081                default:
082                    throw new RuntimeException("@PostConstruct method must be no-arg or 1-arg accepting java.util.Map; method is: " + service.getClass().getName() + "#" + method.getName());
083                }
084                postConstructMethodsByService.put(service, method);
085            }
086            
087            // @PreDestroy
088            for (final Method method : methods) {
089                final PreDestroy preDestroyAnnotation = method.getAnnotation(PreDestroy.class);
090                if(preDestroyAnnotation == null) {
091                    continue;
092                }
093                final Method existing = preDestroyMethodsByService.get(service);
094                if(existing != null) {
095                    throw new RuntimeException("Found more than one @PreDestroy method; service is: " + service.getClass().getName() + ", found " + existing.getName() + " and " + method.getName());
096                }
097                
098                final Class<?>[] parameterTypes = method.getParameterTypes();
099                switch(parameterTypes.length) {
100                case 0:
101                    break;
102                default:
103                    throw new RuntimeException("@PreDestroy method must be no-arg; method is: " + service.getClass().getName() + "#" + method.getName());
104                }
105                preDestroyMethodsByService.put(service, method);
106            }               
107        }
108
109    }
110
111    // //////////////////////////////////////
112
113    public void postConstruct() {
114        LOG.info("calling @PostConstruct on all domain services");
115
116        for (final Map.Entry<Object, Method> entry : postConstructMethodsByService.entrySet()) {
117            final Object service = entry.getKey();
118            final Method method = entry.getValue();
119            
120            LOG.info("... calling @PostConstruct method: " + service.getClass().getName() + ": " + method.getName());
121            
122            final int numParams = method.getParameterTypes().length;
123            
124            // unlike shutdown, we don't swallow exceptions; would rather fail early
125            if(numParams == 0) {
126                MethodExtensions.invoke(method, service);
127            } else {
128                MethodExtensions.invoke(method, service, new Object[]{props});
129            }
130        }
131    }
132
133
134    public void preDestroy() {
135        LOG.info("calling @PreDestroy on all domain services");
136        for (final Map.Entry<Object, Method> entry : preDestroyMethodsByService.entrySet()) {
137            final Object service = entry.getKey();
138            final Method method = entry.getValue();
139            
140            LOG.info("... calling @PreDestroy method: " + service.getClass().getName() + ": " + method.getName());
141            
142            try {
143                MethodExtensions.invoke(method, service);
144            } catch(Exception ex) {
145                // do nothing
146                LOG.warn("... @PreDestroy method threw exception - continuing anyway", ex);
147            }
148        }
149    }
150
151
152}