/* * Copyright (c) 2008, WSO2 Inc. (http://www.wso2.org) All Rights Reserved. * * WSO2 Inc. licenses this file to you under the Apache License, * Version 2.0 (the "License"); you may not use this file except * in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, * software distributed under the License is distributed on an * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY * KIND, either express or implied. See the License for the * specific language governing permissions and limitations * under the License. * */ package org.wso2.carbon.registry.eventing.services; import org.apache.axiom.om.OMElement; import org.apache.axiom.om.impl.builder.StAXOMBuilder; import org.apache.axiom.om.util.AXIOMUtil; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.wso2.carbon.CarbonException; import org.wso2.carbon.context.PrivilegedCarbonContext; import org.wso2.carbon.registry.common.eventing.RegistryEvent; import org.wso2.carbon.registry.common.utils.RegistryUtil; import org.wso2.carbon.registry.core.Resource; import org.wso2.carbon.registry.core.exceptions.RegistryException; import org.wso2.carbon.registry.core.session.UserRegistry; import org.wso2.carbon.registry.event.core.exception.EventBrokerException; import org.wso2.carbon.registry.event.core.subscription.EventDispatcher; import org.wso2.carbon.registry.event.core.subscription.Subscription; import org.wso2.carbon.registry.eventing.RegistryEventDispatcher; import org.wso2.carbon.registry.eventing.RegistryEventingConstants; import org.wso2.carbon.registry.eventing.events.ChildCreatedEvent; import org.wso2.carbon.registry.eventing.events.ChildDeletedEvent; import org.wso2.carbon.registry.eventing.events.CollectionAddedEvent; import org.wso2.carbon.registry.eventing.events.CollectionCreatedEvent; import org.wso2.carbon.registry.eventing.events.CollectionDeletedEvent; import org.wso2.carbon.registry.eventing.events.CollectionUpdatedEvent; import org.wso2.carbon.registry.eventing.events.DispatchEvent; import org.wso2.carbon.registry.eventing.events.ResourceAddedEvent; import org.wso2.carbon.registry.eventing.events.ResourceCreatedEvent; import org.wso2.carbon.registry.eventing.events.ResourceDeletedEvent; import org.wso2.carbon.registry.eventing.events.ResourceUpdatedEvent; import org.wso2.carbon.registry.eventing.internal.EventingDataHolder; import org.wso2.carbon.utils.CarbonUtils; import javax.xml.namespace.QName; import javax.xml.stream.XMLStreamException; import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.io.InputStream; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Collection; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import java.util.TreeMap; import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ExecutorService; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; public class EventingServiceImpl implements EventingService, SubscriptionEmailVerficationService { private Map<String, String[]> eventTypeMap; private Map<String, List<String>> eventTypeExclusionMap; private static boolean initialized = false; private static EventDispatcher dispatcher = null; private ExecutorService executor = null; private String emailIndexStoragePath; public static List<String> listOfMediaTypes = new ArrayList<String>(); private static final String MEDIA_TYPE_MATCHER_FILTER_CLASS = "org.wso2.carbon.registry.core.jdbc.handlers.filters.MediaTypeMatcher"; private static final Log log = LogFactory.getLog(EventingServiceImpl.class); public String verifyEmail(String data) { String subscriptionId = null; String username = null; String remoteURL = null; String email = null; if (data != null) { try { OMElement dataElement = AXIOMUtil.stringToOM(data); Iterator it = dataElement.getChildElements(); while (it.hasNext()) { OMElement element = (OMElement) it.next(); if ("subscriptionId".equals(element.getLocalName())) { subscriptionId = element.getText(); } else if ("username".equals(element.getLocalName())) { username = element.getText(); } else if ("remoteURL".equals(element.getLocalName())) { remoteURL = element.getText(); } else if ("email".equals(element.getLocalName())) { email = element.getText(); } } } catch (XMLStreamException e) { log.error("Unable to verify e-mail address", e); return null; } } try { if (username != null || remoteURL != null) { throw new UnsupportedOperationException("This method is no longer supported"); } else { Subscription subscription = getSubscription(subscriptionId); Map<String, String> subscriptionData = subscription.getProperties(); subscriptionData.remove(RegistryEventingConstants.NOT_VERIFIED); subscription.setProperties(subscriptionData); // The updated subscription should be directly done at the subMgr to avoid // generating another verification request subscription.setId(subscriptionId); EventingDataHolder.getInstance().getRegistryEventBrokerService().renewSubscription(subscription); } return email; } catch (Exception e) { log.error("Unable to verify e-mail address", e); return null; } } public EventingServiceImpl() { try { if (initialized) { return; } else { initialized = true; } eventTypeMap = new TreeMap<String, String[]>(); eventTypeExclusionMap = new HashMap<String, List<String>>(); emailIndexStoragePath="/repository/components/org.wso2.carbon.email-verification/emailIndex"; registerBuiltInEventTypes(); storeHandlerConfig(); } catch (Exception e) { log.error("Error initializing Registry Event Broker"); } } public void registerBuiltInEventTypes() { registerEventType("update", ResourceUpdatedEvent.EVENT_NAME, CollectionUpdatedEvent.EVENT_NAME); registerEventType("delete", ResourceDeletedEvent.EVENT_NAME, CollectionDeletedEvent.EVENT_NAME); registerEventType("child.deleted", null, ChildDeletedEvent.EVENT_NAME); registerEventType("child.created", null, ChildCreatedEvent.EVENT_NAME); } private static void storeHandlerConfig() { String configPath = CarbonUtils.getRegistryXMLPath(); Set<String> hashSet = new HashSet<>(); if (configPath != null) { File registryXML = new File(configPath); try { InputStream configInputStream = new FileInputStream(registryXML); StAXOMBuilder builder = new StAXOMBuilder( CarbonUtils.replaceSystemVariablesInXml(configInputStream)); OMElement configElement = builder.getDocumentElement(); Iterator<OMElement> handlerConfigs = configElement.getChildrenWithName(new QName("handler")); while (handlerConfigs.hasNext()) { OMElement handlerConfigElement = handlerConfigs.next(); OMElement filter = handlerConfigElement.getFirstChildWithName(new QName("filter")); String filterClassName = filter.getAttributeValue(new QName("class")); Class filterClass = Class.forName(filterClassName); Class mediaTypeMatcher = Class.forName(MEDIA_TYPE_MATCHER_FILTER_CLASS); if (filterClass.getGenericSuperclass().equals(mediaTypeMatcher)) { Method method = filterClass.getMethod("getMediaType", null); Object returnValue = method.invoke(filterClass.newInstance(), null); listOfMediaTypes.add((String) returnValue); } OMElement property = filter.getFirstChildWithName(new QName("property")); if (property != null) { if (MEDIA_TYPE_MATCHER_FILTER_CLASS.equals(filterClassName) && "mediaType".equals(property.getAttributeValue(new QName("name")))) { listOfMediaTypes.add(property.getText()); } } } hashSet.addAll(listOfMediaTypes); listOfMediaTypes.clear(); listOfMediaTypes.addAll(hashSet); } catch (FileNotFoundException e) { log.error("registry.xml file not found", e); } catch (CarbonException e) { log.error("Error in converting registry xml inputstream", e); } catch (XMLStreamException e) { log.error("Error in registry xml stream", e); } catch (NoSuchMethodException e) { log.error("No such method to invoke", e); } catch (IllegalAccessException e) { log.error("Error when creating new instance", e); } catch (InstantiationException e) { log.error("Error when creating new instance", e); } catch (InvocationTargetException e) { log.error("Error when invoking the java reflection method", e); } catch (ClassNotFoundException e) { log.error("Error in Class.forName method", e); } } } public void notify(RegistryEvent event) throws Exception { notify(event, null); } public void notify(RegistryEvent event, String endpoint) throws Exception { notify(event, endpoint, false); } private synchronized static void initializeDispatcher() { if (dispatcher == null) { dispatcher = new RegistryEventDispatcher(); ((RegistryEventDispatcher)dispatcher).init(EventingDataHolder.getInstance().getConfigurationContext()); } } public void notify(RegistryEvent event, String endpoint, boolean doRest) throws Exception { if (!initialized) { return; } if (executor == null) { setupExecutorService(); } if (dispatcher == null) { try { initializeDispatcher(); } catch (IllegalStateException ignored) { // We throw an Illegal State Exception only if the dispatcher failed to initialize. // There is no point in continuing after that. Such a scenario can happen if the // very first event was generated during shutdown and we are unable to create // corresponding dispatch queues. return; } } executor.submit(new Publisher(new DispatchEvent(event, endpoint, doRest))); } public void registerEventType(String typeId, String resourceEvent, String collectionEvent) { String[] eventNames = new String[2]; eventNames[0] = resourceEvent; eventNames[1] = collectionEvent; eventTypeMap.put(typeId, eventNames); } public List<Subscription> getAllSubscriptions() throws EventBrokerException { return EventingDataHolder.getInstance().getRegistryEventBrokerService().getAllSubscriptions(null); } public List<Subscription> getAllSubscriptions(String userName, String remoteURL) throws EventBrokerException { throw new UnsupportedOperationException("This method is no longer supported"); } public Map getEventTypes() { return eventTypeMap; } public String getSubscriptionManagerUrl() { return EventingDataHolder.getInstance().getDefaultEventingServiceURL(); } public Subscription getSubscription(String id) { try { if (id != null && id.contains(";version:")) { log.warn( "Versioned resources cannot have subscriptions, instead returns the subscription from the actual resource"); return EventingDataHolder.getInstance().getRegistryEventBrokerService() .getSubscription(RegistryUtil.getResourcePathFromVersionPath(id)); } else { return EventingDataHolder.getInstance().getRegistryEventBrokerService().getSubscription(id); } } catch (EventBrokerException e) { log.error("Unable to get subscription for given id: " + id, e); return null; } } private void requestEmailVerification(Subscription subscription, String userName, String remoteURL) { int tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); if (executor == null) { setupExecutorService(); } if(!subscription.getProperties().isEmpty()){ executor.submit(new EmailVerifier(subscription, userName, remoteURL, tenantId)); } } private synchronized void setupExecutorService() { if (executor == null) { executor = new ThreadPoolExecutor(25, 150, 1000, TimeUnit.NANOSECONDS, new ArrayBlockingQueue<Runnable>(100)); } } public String subscribe(Subscription subscription, String userName, String remoteURL) { throw new UnsupportedOperationException("This method is no longer supported"); } public String subscribe(Subscription subscription) { try { boolean emailAvailability = isEmailAlreadyAvailable(subscription); if ((subscription.getEventSinkURL().startsWith("mailto:") || subscription.getEventSinkURL().startsWith("digest:")) && !emailAvailability) { Map<String, String> subscriptionData = subscription.getProperties(); subscriptionData.put(RegistryEventingConstants.NOT_VERIFIED, Boolean.toString(true)); subscription.setProperties(subscriptionData); } String subscribe = EventingDataHolder.getInstance().getRegistryEventBrokerService().subscribe(subscription); requestEmailVerification(subscription, null, null); return subscribe; } catch (EventBrokerException e) { log.error("Unable to add subscription", e); return null; } } private boolean isEmailAlreadyAvailable(Subscription subscription) { // if one-time email verification is false or not defined, we do not need to check resource, and we will treat it as the resource was // not available. if (Boolean.parseBoolean(System.getProperty("onetime.email.verification", Boolean.toString(false)))) { String email = subscription.getEventSinkURL().substring(subscription.getEventSinkURL().indexOf(":") + 1 , subscription.getEventSinkURL().length()); try { UserRegistry registry = EventingDataHolder.getInstance().getRegistryService().getConfigSystemRegistry(); String emailIndexPath = this.emailIndexStoragePath; Resource emailIndexResource; if (registry.resourceExists(emailIndexPath)) { emailIndexResource = registry.get(emailIndexPath); Collection<Object> values = emailIndexResource.getProperties().values(); for (Iterator it = values.iterator(); it.hasNext(); ) { String value = (((ArrayList) (it.next())).toArray())[0].toString(); if (value.equals(email)) { return true; } } } else { emailIndexResource = registry.newResource(); registry.put(emailIndexPath, emailIndexResource); } } catch (RegistryException e) { log.error("Unable to check email verification resource", e); } } return false; } public Subscription getSubscription(String id, String userName, String remoteURL) { throw new UnsupportedOperationException("This method is no longer supported"); } public boolean unsubscribe(String subscriptionID) { try { EventingDataHolder.getInstance().getRegistryEventBrokerService().unsubscribe(subscriptionID); return true; } catch (EventBrokerException e) { log.error("Unable to unsubscribe using given id: " + subscriptionID, e); return false; } } public boolean unsubscribe(String subscriptionID, String userName, String remoteURL) { throw new UnsupportedOperationException("This method is no longer supported"); } public void registerEventTypeExclusion(String typeId, String path) { if (path == null) { return; } List<String> list = eventTypeExclusionMap.get(typeId); if (list == null) { list = new LinkedList<String>(); } list.add(path); eventTypeExclusionMap.put(typeId, list); } public boolean isEventTypeExclusionRegistered(String typeId, String path) { List<String> list = eventTypeExclusionMap.get(typeId); if (list == null) { return false; } for(String s: list) { if (path.matches(s)) { return true; } } return false; } private static class EmailVerifier implements Runnable { private Subscription subscription; private String userName; private String remoteURL; private int tenantId; private String tenantDomain; public EmailVerifier(Subscription subscription, String userName, String remoteURL, int tenantId) { this.subscription = subscription; this.userName = userName; this.remoteURL = remoteURL; this.tenantId = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantId(); this.tenantDomain = PrivilegedCarbonContext.getThreadLocalCarbonContext().getTenantDomain(); } @SuppressWarnings("unchecked") public void run() { PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantDomain(tenantDomain); PrivilegedCarbonContext.getThreadLocalCarbonContext().setTenantId(tenantId, true); if (EventingDataHolder.getInstance().getEmailVerificationSubscriber() == null) { return; } String eventUrl = subscription.getEventSinkURL(); String lowerCasedEventUrl=eventUrl.toLowerCase(); String email = ""; if (lowerCasedEventUrl.startsWith("digest:")&& lowerCasedEventUrl.contains("mailto")) { // extracting the mailto: address form eventsink url like digest://h/mailto:email email = eventUrl.substring("digest:".length() +4); } // user verification for emails. else if(lowerCasedEventUrl.contains("mailto:")) { email = eventUrl; } // we are not requesting verifications for user and roles. else{ return; } email = email.substring("mailto:".length()); Map<String,String> data = new HashMap<String, String>(); data.put("email", email); data.put("subscriptionId", subscription.getId()); if (userName != null) { data.put("username", userName); } if (remoteURL != null) { data.put("remoteURL", remoteURL); } try { EventingDataHolder.getInstance().getEmailVerificationSubscriber().requestUserVerification(data, EventingDataHolder.getInstance().getEmailVerifierConfig()); } catch (Exception e) { log.error("Unable to create e-mail verification request", e); } } } private static class Publisher implements Runnable { private DispatchEvent event; public Publisher(DispatchEvent event) { this.event = event; } public void run() { try { // Setup the Carbon Context so that downstream logic can make use of it. // At present, the Event Component makes use of the information stored on the Carbon // Context when publishing events. PrivilegedCarbonContext.startTenantFlow(); try { PrivilegedCarbonContext context = PrivilegedCarbonContext.getThreadLocalCarbonContext(); int tenantId = event.getTenantId(); if (tenantId != -1) { context.setTenantId(tenantId, true); } RegistryEvent.RegistrySession registrySessionDetails = event.getRegistrySessionDetails(); if (registrySessionDetails != null) { String username = registrySessionDetails.getUsername(); if (username != null) { context.setUsername(username); } } // For each resource-event also generate a corresponding collection event, so that any hierarchical subscriptions would // work. if (event.getTopic().contains(ResourceUpdatedEvent.EVENT_NAME)) { EventingDataHolder.getInstance().getRegistryEventBrokerService() .publish(event, event.getTopic()); EventingDataHolder.getInstance().getRegistryEventBrokerService().publish(event, event.getTopic() .replace(ResourceUpdatedEvent.EVENT_NAME, CollectionUpdatedEvent.EVENT_NAME)); } else if (event.getTopic().contains(ResourceAddedEvent.EVENT_NAME)) { EventingDataHolder.getInstance().getRegistryEventBrokerService() .publish(event, event.getTopic()); EventingDataHolder.getInstance().getRegistryEventBrokerService().publish(event, event.getTopic() .replace(ResourceAddedEvent.EVENT_NAME, CollectionAddedEvent.EVENT_NAME)); } else if (event.getTopic().contains(ResourceCreatedEvent.EVENT_NAME)) { EventingDataHolder.getInstance().getRegistryEventBrokerService() .publish(event, event.getTopic()); EventingDataHolder.getInstance().getRegistryEventBrokerService().publish(event, event.getTopic() .replace(ResourceCreatedEvent.EVENT_NAME, CollectionCreatedEvent.EVENT_NAME)); } else if (event.getTopic().contains(ResourceDeletedEvent.EVENT_NAME)) { EventingDataHolder.getInstance().getRegistryEventBrokerService().publish(event, event.getTopic()); EventingDataHolder.getInstance().getRegistryEventBrokerService().publish(event, event.getTopic() .replace(ResourceDeletedEvent.EVENT_NAME, CollectionDeletedEvent.EVENT_NAME)); } else { EventingDataHolder.getInstance().getRegistryEventBrokerService() .publish(event, event.getTopic()); } } finally { PrivilegedCarbonContext.endTenantFlow(); } } catch (EventBrokerException e) { log.error("Unable to send notification", e); } } } }