| /* |
| * 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.brooklyn.entity.proxy.nginx; |
| |
| import static org.apache.brooklyn.util.JavaGroovyEquivalents.groovyTruth; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.Set; |
| |
| import org.apache.brooklyn.api.entity.Entity; |
| import org.apache.brooklyn.api.mgmt.SubscriptionHandle; |
| import org.apache.brooklyn.api.sensor.SensorEvent; |
| import org.apache.brooklyn.api.sensor.SensorEventListener; |
| import org.apache.brooklyn.core.entity.Attributes; |
| import org.apache.brooklyn.core.entity.Entities; |
| import org.apache.brooklyn.core.entity.EntityPredicates; |
| import org.apache.brooklyn.core.entity.trait.Changeable; |
| import org.apache.brooklyn.core.entity.trait.Startable; |
| import org.apache.brooklyn.entity.group.AbstractGroupImpl; |
| import org.apache.brooklyn.entity.proxy.ProxySslConfig; |
| import org.apache.brooklyn.entity.webapp.WebAppServiceConstants; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.common.base.Preconditions; |
| import com.google.common.collect.ImmutableSet; |
| import com.google.common.collect.Sets; |
| |
| /** |
| * This is a group whose members will be made available to a load-balancer / URL forwarding service (such as nginx). |
| * <p> |
| * Configuration requires a <b>domain</b> and some mechanism for finding members. |
| * The easiest way to find members is using a <b>target</b> whose children will be tracked, |
| * but alternative membership policies can also be used. |
| */ |
| public class UrlMappingImpl extends AbstractGroupImpl implements UrlMapping { |
| |
| private static final Logger log = LoggerFactory.getLogger(UrlMapping.class); |
| |
| public UrlMappingImpl() { |
| super(); |
| } |
| |
| @Override |
| public String getUniqueLabel() { |
| String l = getConfig(LABEL); |
| if (groovyTruth(l)) return getId()+"-"+l; |
| else return getId(); |
| } |
| |
| /** adds a rewrite rule, must be called at config time. see {@link UrlRewriteRule} for more info. */ |
| @Override |
| public synchronized UrlMapping addRewrite(String from, String to) { |
| return addRewrite(new UrlRewriteRule(from, to)); |
| } |
| |
| /** adds a rewrite rule, must be called at config time. see {@link UrlRewriteRule} for more info. */ |
| @Override |
| public synchronized UrlMapping addRewrite(UrlRewriteRule rule) { |
| Collection<UrlRewriteRule> rewrites = getConfig(REWRITES); |
| if (rewrites==null) { |
| rewrites = new ArrayList<UrlRewriteRule>(); |
| } |
| rewrites.add(rule); |
| config().set(REWRITES, rewrites); |
| return this; |
| } |
| |
| @Override |
| public String getDomain() { |
| return Preconditions.checkNotNull( getConfig(DOMAIN), "domain config argument required"); |
| } |
| |
| @Override |
| public String getPath() { |
| return getConfig(PATH); |
| } |
| |
| @Override |
| public Entity getTarget() { |
| return getConfig(TARGET_PARENT); |
| } |
| |
| @Override |
| public void setTarget(Entity target) { |
| config().set(TARGET_PARENT, target); |
| recompute(); |
| } |
| |
| @Override |
| public void onManagementStarting() { |
| super.onManagementStarting(); |
| |
| if (getConfig(TARGET_PARENT) != null) { |
| recompute(); |
| // following line could be more efficient (just modify the addresses set, not clearing it each time; |
| // but since addresses is lazy loaded not that big a deal) |
| // subscribe(this, Changeable.GROUP_SIZE, { resetAddresses(true) } as SensorEventListener); |
| // above not needed since our target tracking figures this out |
| } |
| } |
| |
| /** defines how address string, ie hostname:port, is constructed from a given entity. |
| * returns null if not possible. |
| * <p> |
| * the default is to look at HOSTNAME and HTTPS_PORT or HTTP_PORT attribute sensors (depending on SSL_CONFIG being set with targetIsSsl). |
| * <p> |
| * this method is suitable (intended) for overriding if needed. |
| */ |
| protected String getAddressOfEntity(Entity s) { |
| String h = s.getAttribute(Attributes.HOSTNAME); |
| |
| Integer p = null; |
| Set<String> protos = s.getAttribute(WebAppServiceConstants.ENABLED_PROTOCOLS); |
| ProxySslConfig sslConfig = getConfig(SSL_CONFIG); |
| if (sslConfig != null && sslConfig.getTargetIsSsl()) { |
| // use ssl |
| if (protos != null && hasProtocol(protos, "https")) { |
| // proto configured correctly |
| } else { |
| // proto not defined; use https anyway, but it might fail |
| log.warn("Misconfiguration for "+this+": ENABLED_PROTOCOLS='"+protos+"' for "+s+" but sslConfig="+sslConfig); |
| } |
| p = s.getAttribute(Attributes.HTTPS_PORT); |
| if (p == null) |
| log.warn("Misconfiguration for "+this+": sslConfig="+sslConfig+" but no HTTPS_PORT on "+s); |
| } |
| if (p == null) { |
| // default to http |
| p = s.getAttribute(Attributes.HTTP_PORT); |
| } |
| |
| if (groovyTruth(h) && p != null) return h+":"+p; |
| log.error("Unable to construct hostname:port representation for "+s+"; skipping in "+this); |
| return null; |
| } |
| |
| protected synchronized void recomputeAddresses() { |
| Set<String> resultM = Sets.newLinkedHashSet(); |
| for (Entity s: getMembers()) { |
| String hp = getAddressOfEntity(s); |
| if (hp != null) resultM.add(hp); |
| } |
| Set<String> result = Collections.unmodifiableSet(resultM); |
| Collection<String> oldAddresses = getAttribute(TARGET_ADDRESSES); |
| if (oldAddresses == null || !result.equals(ImmutableSet.copyOf(oldAddresses))) { |
| sensors().set(TARGET_ADDRESSES, result); |
| } |
| } |
| |
| @Override |
| public Collection<String> getTargetAddresses() { |
| return getAttribute(TARGET_ADDRESSES); |
| } |
| |
| @Override |
| public ProxySslConfig getSsl() { |
| return getConfig(SSL_CONFIG); |
| } |
| |
| // FIXME Do we really need this?! |
| protected SubscriptionHandle getSubscriptionHandle() { |
| return subscriptionHandle; |
| } |
| |
| private SubscriptionHandle subscriptionHandle; |
| private SubscriptionHandle subscriptionHandle2; |
| |
| @Override |
| public synchronized void recompute() { |
| if (subscriptionHandle != null) subscriptions().unsubscribe(subscriptionHandle); |
| if (subscriptionHandle2 != null) subscriptions().unsubscribe(subscriptionHandle2); |
| |
| Entity t = getTarget(); |
| if (t != null) { |
| subscriptionHandle = subscriptions().subscribeToChildren(t, Startable.SERVICE_UP, new SensorEventListener<Boolean>() { |
| @Override public void onEvent(SensorEvent<Boolean> event) { |
| boolean changed = (event.getValue()) ? addMember(event.getSource()) : removeMember(event.getSource()); |
| if (changed) { |
| recomputeAddresses(); |
| } |
| }}); |
| subscriptionHandle2 = subscriptions().subscribe(t, Changeable.MEMBER_REMOVED, new SensorEventListener<Entity>() { |
| @Override public void onEvent(SensorEvent<Entity> event) { |
| removeMember(event.getValue()); |
| // recompute, irrespective of change, because framework may have already invoked the removeMember call |
| recomputeAddresses(); |
| }}); |
| setMembers(t.getChildren(), EntityPredicates.attributeEqualTo(Startable.SERVICE_UP, true)); |
| } |
| |
| recomputeAddresses(); |
| } |
| |
| @Override |
| public void discard() { |
| Entities.unmanage(this); |
| } |
| |
| private boolean hasProtocol(Collection<String> protocols, String desired) { |
| for (String contender : protocols) { |
| if ("https".equals(contender.toLowerCase())) return true; |
| } |
| return false; |
| } |
| } |