1// Licensed under Apache License version 2.0
2package javax.jmdns.impl.tasks.state;
3
4import java.io.IOException;
5import java.util.ArrayList;
6import java.util.List;
7import java.util.logging.Level;
8import java.util.logging.Logger;
9
10import javax.jmdns.ServiceInfo;
11import javax.jmdns.impl.DNSOutgoing;
12import javax.jmdns.impl.DNSStatefulObject;
13import javax.jmdns.impl.JmDNSImpl;
14import javax.jmdns.impl.ServiceInfoImpl;
15import javax.jmdns.impl.constants.DNSConstants;
16import javax.jmdns.impl.constants.DNSState;
17import javax.jmdns.impl.tasks.DNSTask;
18
19/**
20 * This is the root class for all state tasks. These tasks work with objects that implements the {@link javax.jmdns.impl.DNSStatefulObject} interface and therefore participate in the state machine.
21 *
22 * @author Pierre Frisch
23 */
24public abstract class DNSStateTask extends DNSTask {
25    static Logger      logger1     = Logger.getLogger(DNSStateTask.class.getName());
26
27    /**
28     * By setting a 0 ttl we effectively expire the record.
29     */
30    private final int  _ttl;
31
32    private static int _defaultTTL = DNSConstants.DNS_TTL;
33
34    /**
35     * The state of the task.
36     */
37    private DNSState   _taskState  = null;
38
39    public abstract String getTaskDescription();
40
41    public static int defaultTTL() {
42        return _defaultTTL;
43    }
44
45    /**
46     * For testing only do not use in production.
47     *
48     * @param value
49     */
50    public static void setDefaultTTL(int value) {
51        _defaultTTL = value;
52    }
53
54    /**
55     * @param jmDNSImpl
56     * @param ttl
57     */
58    public DNSStateTask(JmDNSImpl jmDNSImpl, int ttl) {
59        super(jmDNSImpl);
60        _ttl = ttl;
61    }
62
63    /**
64     * @return the ttl
65     */
66    public int getTTL() {
67        return _ttl;
68    }
69
70    /**
71     * Associate the DNS host and the service infos with this task if not already associated and in the same state.
72     *
73     * @param state
74     *            target state
75     */
76    protected void associate(DNSState state) {
77        synchronized (this.getDns()) {
78            this.getDns().associateWithTask(this, state);
79        }
80        for (ServiceInfo serviceInfo : this.getDns().getServices().values()) {
81            ((ServiceInfoImpl) serviceInfo).associateWithTask(this, state);
82        }
83    }
84
85    /**
86     * Remove the DNS host and service info association with this task.
87     */
88    protected void removeAssociation() {
89        // Remove association from host to this
90        synchronized (this.getDns()) {
91            this.getDns().removeAssociationWithTask(this);
92        }
93
94        // Remove associations from services to this
95        for (ServiceInfo serviceInfo : this.getDns().getServices().values()) {
96            ((ServiceInfoImpl) serviceInfo).removeAssociationWithTask(this);
97        }
98    }
99
100    @Override
101    public void run() {
102        DNSOutgoing out = this.createOugoing();
103        try {
104            if (!this.checkRunCondition()) {
105                this.cancel();
106                return;
107            }
108            List<DNSStatefulObject> stateObjects = new ArrayList<DNSStatefulObject>();
109            // send probes for JmDNS itself
110            synchronized (this.getDns()) {
111                if (this.getDns().isAssociatedWithTask(this, this.getTaskState())) {
112                    logger1.finer(this.getName() + ".run() JmDNS " + this.getTaskDescription() + " " + this.getDns().getName());
113                    stateObjects.add(this.getDns());
114                    out = this.buildOutgoingForDNS(out);
115                }
116            }
117            // send probes for services
118            for (ServiceInfo serviceInfo : this.getDns().getServices().values()) {
119                ServiceInfoImpl info = (ServiceInfoImpl) serviceInfo;
120
121                synchronized (info) {
122                    if (info.isAssociatedWithTask(this, this.getTaskState())) {
123                        logger1.fine(this.getName() + ".run() JmDNS " + this.getTaskDescription() + " " + info.getQualifiedName());
124                        stateObjects.add(info);
125                        out = this.buildOutgoingForInfo(info, out);
126                    }
127                }
128            }
129            if (!out.isEmpty()) {
130                logger1.finer(this.getName() + ".run() JmDNS " + this.getTaskDescription() + " #" + this.getTaskState());
131                this.getDns().send(out);
132
133                // Advance the state of objects.
134                this.advanceObjectsState(stateObjects);
135            } else {
136                // Advance the state of objects.
137                this.advanceObjectsState(stateObjects);
138
139                // If we have nothing to send, another timer taskState ahead of us has done the job for us. We can cancel.
140                cancel();
141                return;
142            }
143        } catch (Throwable e) {
144            logger1.log(Level.WARNING, this.getName() + ".run() exception ", e);
145            this.recoverTask(e);
146        }
147
148        this.advanceTask();
149    }
150
151    protected abstract boolean checkRunCondition();
152
153    protected abstract DNSOutgoing buildOutgoingForDNS(DNSOutgoing out) throws IOException;
154
155    protected abstract DNSOutgoing buildOutgoingForInfo(ServiceInfoImpl info, DNSOutgoing out) throws IOException;
156
157    protected abstract DNSOutgoing createOugoing();
158
159    protected void advanceObjectsState(List<DNSStatefulObject> list) {
160        if (list != null) {
161            for (DNSStatefulObject object : list) {
162                synchronized (object) {
163                    object.advanceState(this);
164                }
165            }
166        }
167    }
168
169    protected abstract void recoverTask(Throwable e);
170
171    protected abstract void advanceTask();
172
173    /**
174     * @return the taskState
175     */
176    protected DNSState getTaskState() {
177        return this._taskState;
178    }
179
180    /**
181     * @param taskState
182     *            the taskState to set
183     */
184    protected void setTaskState(DNSState taskState) {
185        this._taskState = taskState;
186    }
187
188}
189