/** * Licensed to the Apache Software Foundation (ASF) under one * or more contributor license agreements. See the NOTICE file * distributed with this work for additional information * regarding copyright ownership. The ASF 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.apache.cxf.ws.eventing.backend.manager; import java.util.Collections; import java.util.GregorianCalendar; import java.util.List; import java.util.UUID; import java.util.logging.Logger; import javax.xml.bind.JAXBElement; import javax.xml.datatype.DatatypeConfigurationException; import javax.xml.datatype.DatatypeFactory; import javax.xml.datatype.XMLGregorianCalendar; import javax.xml.namespace.QName; import org.apache.cxf.binding.soap.SoapFault; import org.apache.cxf.common.logging.LogUtils; import org.apache.cxf.ws.addressing.AttributedURIType; import org.apache.cxf.ws.addressing.EndpointReferenceType; import org.apache.cxf.ws.addressing.ReferenceParametersType; import org.apache.cxf.ws.eventing.DeliveryType; import org.apache.cxf.ws.eventing.ExpirationType; import org.apache.cxf.ws.eventing.FilterType; import org.apache.cxf.ws.eventing.FormatType; import org.apache.cxf.ws.eventing.backend.database.SubscriptionDatabase; import org.apache.cxf.ws.eventing.backend.database.SubscriptionDatabaseImpl; import org.apache.cxf.ws.eventing.backend.database.SubscriptionTicket; import org.apache.cxf.ws.eventing.backend.notification.NotificatorService; import org.apache.cxf.ws.eventing.backend.notification.SubscriptionEndStatus; import org.apache.cxf.ws.eventing.shared.EventingConstants; import org.apache.cxf.ws.eventing.shared.faults.CannotProcessFilter; import org.apache.cxf.ws.eventing.shared.faults.DeliveryFormatRequestedUnavailable; import org.apache.cxf.ws.eventing.shared.faults.FilteringRequestedUnavailable; import org.apache.cxf.ws.eventing.shared.faults.NoDeliveryMechanismEstablished; import org.apache.cxf.ws.eventing.shared.faults.UnknownSubscription; import org.apache.cxf.ws.eventing.shared.utils.DurationAndDateUtil; import org.apache.cxf.ws.eventing.shared.utils.EPRInspectionTool; import org.apache.cxf.ws.eventing.shared.utils.FilteringUtil; /** * The core class representing WS-Eventing backend. It holds an instance of a database and * acts as a layer for communicating with it. */ public class SubscriptionManagerImpl implements SubscriptionManager { protected static final Logger LOG = LogUtils.getLogger(SubscriptionManagerImpl.class); protected final SubscriptionDatabase database; private final String subscriptionIdNamespace; private final String subscriptionIdElementName; private String url; private NotificatorService notificator; public SubscriptionManagerImpl(String url) { database = new SubscriptionDatabaseImpl(); this.subscriptionIdNamespace = EventingConstants.SUBSCRIPTION_ID_DEFAULT_NAMESPACE; this.subscriptionIdElementName = EventingConstants.SUBSCRIPTION_ID_DEFAULT_ELEMENT_NAME; this.url = url; } public SubscriptionManagerImpl(String url, String namespace, String elementName) { database = new SubscriptionDatabaseImpl(); this.url = url; this.subscriptionIdNamespace = namespace; this.subscriptionIdElementName = elementName; } @Override public SubscriptionTicketGrantingResponse subscribe(DeliveryType delivery, EndpointReferenceType endTo, ExpirationType expires, FilterType filter, FormatType format) { SubscriptionTicket ticket = new SubscriptionTicket(); SubscriptionTicketGrantingResponse response = new SubscriptionTicketGrantingResponse(); grantSubscriptionManagerReference(ticket, response); processDelivery(delivery, ticket, response); processEndTo(endTo, ticket, response); processExpiration(expires, ticket, response); processFilters(filter, ticket, response); processFormat(format, ticket, response); getDatabase().addTicket(ticket); return response; } @Override public List<SubscriptionTicket> getTickets() { return Collections.unmodifiableList(database.getTickets()); } protected SubscriptionDatabase getDatabase() { return database; } protected void processFormat(FormatType format, SubscriptionTicket ticket, SubscriptionTicketGrantingResponse response) { if (format == null) { ticket.setWrappedDelivery(false); return; } if (format.getName().equals(EventingConstants.DELIVERY_FORMAT_WRAPPED)) { LOG.info("[subscription=" + ticket.getUuid() + "] Wrapped delivery format was requested."); ticket.setWrappedDelivery(true); } else if (format.getName().equals(EventingConstants.DELIVERY_FORMAT_UNWRAPPED)) { LOG.info("[subscription=" + ticket.getUuid() + "] Wrapped delivery format was NOT requested."); ticket.setWrappedDelivery(false); } else { LOG.info("[subscription=" + ticket.getUuid() + "] Unknown delivery format: " + format.getName()); throw new DeliveryFormatRequestedUnavailable(); } } protected void processFilters(FilterType request, SubscriptionTicket ticket, SubscriptionTicketGrantingResponse response) { if (request != null) { // test if the requested filtering dialect is supported if (FilteringUtil.isFilteringDialectSupported(request.getDialect())) { String filter = (String)request.getContent().get(0); LOG.fine("Found filter content: " + filter); if (!FilteringUtil.isValidFilter(filter)) { throw new CannotProcessFilter(); } ticket.setFilter(request); } else { throw new FilteringRequestedUnavailable(); } } } /** * process the stuff concerning expiration request (wse:Expires) */ protected void processExpiration(ExpirationType request, SubscriptionTicket ticket, SubscriptionTicketGrantingResponse response) { XMLGregorianCalendar granted; if (request != null) { Object expirationTypeValue; try { expirationTypeValue = DurationAndDateUtil.parseDurationOrTimestamp(request.getValue()); } catch (IllegalArgumentException ex) { throw new SoapFault("Cannot parse expiration", new QName("http://cxf.apache.org/eventing", "Error")); } Boolean bestEffort = request.isBestEffort(); if (bestEffort != null && bestEffort) { if (expirationTypeValue instanceof javax.xml.datatype.Duration) { granted = grantExpirationFor((javax.xml.datatype.Duration)expirationTypeValue); } else if (expirationTypeValue instanceof XMLGregorianCalendar) { granted = grantExpirationFor((XMLGregorianCalendar)expirationTypeValue); } else { throw new Error("expirationTypeValue of unexpected type: " + expirationTypeValue.getClass()); } } else { // client did not specify BestEffort granting, so we must either follow their wish // or throw a UnsupportedExpirationValue fault if (expirationTypeValue instanceof javax.xml.datatype.Duration) { try { if (DurationAndDateUtil.isPT0S((javax.xml.datatype.Duration)expirationTypeValue)) { ticket.setNonExpiring(true); } granted = DatatypeFactory.newInstance().newXMLGregorianCalendar(new GregorianCalendar()); granted.add((javax.xml.datatype.Duration)expirationTypeValue); } catch (DatatypeConfigurationException e) { throw new Error(e); } } else if (expirationTypeValue instanceof XMLGregorianCalendar) { granted = (XMLGregorianCalendar)expirationTypeValue; } else { throw new Error("expirationTypeValue of unexpected type: " + expirationTypeValue.getClass()); } } } else { granted = grantExpiration(); } ticket.setExpires(granted); response.setExpires(granted); LOG.info("[subscription=" + ticket.getUuid() + "] Granted Expiration date: " + granted.toString()); } protected void processEndTo(EndpointReferenceType request, SubscriptionTicket ticket, SubscriptionTicketGrantingResponse response) { if (request != null) { ticket.setEndTo(request); } } protected void processDelivery(DeliveryType request, SubscriptionTicket ticket, SubscriptionTicketGrantingResponse response) { // check if there is any usable EPR in the Delivery part try { @SuppressWarnings("unchecked") JAXBElement<EndpointReferenceType> notifyTo = (JAXBElement<EndpointReferenceType>)request.getContent().get(0); if (!EPRInspectionTool.containsUsableEPR(notifyTo.getValue())) { throw new NoDeliveryMechanismEstablished(); } } catch (NullPointerException npe) { throw new NoDeliveryMechanismEstablished(); } catch (IndexOutOfBoundsException ioobe) { throw new NoDeliveryMechanismEstablished(); } ticket.setDelivery(request); } protected void grantSubscriptionManagerReference(SubscriptionTicket ticket, SubscriptionTicketGrantingResponse response) { EndpointReferenceType subscriptionManagerReference = new EndpointReferenceType(); subscriptionManagerReference.setAddress(getSubscriptionManagerAddress()); // generate a ID for this subscription UUID uuid = UUID.randomUUID(); JAXBElement<String> idqn = new JAXBElement<String>(new QName(subscriptionIdNamespace, subscriptionIdElementName), String.class, uuid.toString()); subscriptionManagerReference.setReferenceParameters(new ReferenceParametersType()); subscriptionManagerReference.getReferenceParameters().getAny().add(idqn); ticket.setUuid(uuid); response.setSubscriptionManagerReference(subscriptionManagerReference); response.setUUID(uuid); } /** * Decide what expiration time to grant to the subscription, if * the client specified a calendar time in the request and did specify BestEffort=true. */ public XMLGregorianCalendar grantExpirationFor(XMLGregorianCalendar requested) { return requested; // default } /** * Decide what expiration time to grant to the subscription, if * the client specified a duration in the request and did specify BestEffort=true. */ public XMLGregorianCalendar grantExpirationFor(javax.xml.datatype.Duration requested) { XMLGregorianCalendar granted; try { granted = DatatypeFactory.newInstance().newXMLGregorianCalendar(new GregorianCalendar()); if (DurationAndDateUtil .isPT0S(requested)) { // The client requested a non-expiring subscription. // We will give them 5 years. granted.add(DatatypeFactory.newInstance().newDurationYearMonth(true, 5, 0)); } else { granted.add(requested); // default } return granted; } catch (DatatypeConfigurationException e) { throw new Error(e); } } /** * Decide what expiration time to grant to the subscription, if * the client did not specify any particular wish for subscription length. */ public XMLGregorianCalendar grantExpiration() { try { // by default, we grant an expiration time of 2 years DatatypeFactory factory = DatatypeFactory.newInstance(); XMLGregorianCalendar granted = factory.newXMLGregorianCalendar(new GregorianCalendar()); granted.add(factory.newDurationYearMonth(true, 2, 0)); return granted; } catch (DatatypeConfigurationException ex) { throw new Error(ex); } } public AttributedURIType getSubscriptionManagerAddress() { AttributedURIType ret = new AttributedURIType(); ret.setValue(url); return ret; } @Override public void unsubscribeTicket(UUID uuid) { getDatabase().removeTicketByUUID(uuid); } @Override public SubscriptionTicket findTicket(UUID uuid) { return getDatabase().findById(uuid); } @Override public ExpirationType renew(UUID uuid, ExpirationType requestedExpiration) { SubscriptionTicket ticket = getDatabase().findById(uuid); if (ticket == null) { throw new UnknownSubscription(); } LOG.info("[subscription=" + ticket.getUuid() + "] Requested renew expiration: " + requestedExpiration.getValue()); LOG.fine("[subscription=" + ticket.getUuid() + "] Current expiration: " + ticket.getExpires().toXMLFormat()); ExpirationType response = new ExpirationType(); XMLGregorianCalendar grantedExpires; if (DurationAndDateUtil.isDuration(requestedExpiration.getValue())) { // duration was requested javax.xml.datatype.Duration requestedDuration = DurationAndDateUtil .parseDuration(requestedExpiration.getValue()); javax.xml.datatype.Duration grantedDuration = requestedDuration; LOG.info("[subscription=" + ticket.getUuid() + "] Granted renewal duration: " + grantedDuration.toString()); grantedExpires = getDatabase().findById(uuid) .getExpires(); // NOW() or current Expires() ???? grantedExpires.add(grantedDuration); response.setValue(grantedDuration.toString()); } else { // end-date was requested grantedExpires = DurationAndDateUtil.parseXMLGregorianCalendar(requestedExpiration.getValue()); LOG.info("[subscription=" + ticket.getUuid() + "] Granted expiration: " + grantedExpires.toXMLFormat()); response.setValue(grantedExpires.toXMLFormat()); } getDatabase().findById(uuid).setExpires(grantedExpires); return response; } @Override public void subscriptionEnd(UUID subscriptionId, String reason, SubscriptionEndStatus status) { synchronized (database) { SubscriptionTicket ticket = database.findById(subscriptionId); if (ticket != null) { database.removeTicketByUUID(subscriptionId); if (ticket.getEndToURL() != null) { notificator.subscriptionEnd(ticket, reason, status); } } else { LOG.severe("No such subscription: " + subscriptionId); } } } @Override public void registerNotificator(NotificatorService service) { this.notificator = service; } }