();
78 | }
79 | }
80 |
81 | /****
82 | * {@link GtfsRealtimeSink} Interface
83 | ****/
84 |
85 | @Override
86 | public synchronized void setFeedHeaderDefaults(FeedHeader header) {
87 | _header = header;
88 | _cachedFeed = null;
89 | }
90 |
91 | @Override
92 | public synchronized void handleFullUpdate(GtfsRealtimeFullUpdate update) {
93 | _cachedFeed = null;
94 | _feedEntities.clear();
95 | for (FeedEntity entity : update.getEntities()) {
96 | _feedEntities.put(entity.getId(), entity);
97 | }
98 | _incrementalIndex++;
99 | FeedMessage feed = getFeed();
100 | for (GtfsRealtimeIncrementalListener listener : _listeners) {
101 | listener.handleFeed(feed);
102 | }
103 | }
104 |
105 | @Override
106 | public synchronized void handleIncrementalUpdate(
107 | GtfsRealtimeIncrementalUpdate update) {
108 | _cachedFeed = null;
109 |
110 | for (FeedEntity toAdd : update.getUpdatedEntities()) {
111 | _feedEntities.put(toAdd.getId(), toAdd);
112 | }
113 | for (String toRemove : update.getDeletedEntities()) {
114 | _feedEntities.remove(toRemove);
115 | }
116 |
117 | FeedMessage.Builder feed = FeedMessage.newBuilder();
118 | feed.setHeader(createIncrementalHeader());
119 | feed.addAllEntity(update.getUpdatedEntities());
120 | for (String toRemove : update.getDeletedEntities()) {
121 | FeedEntity.Builder entity = FeedEntity.newBuilder();
122 | entity.setIsDeleted(true);
123 | entity.setId(toRemove);
124 | feed.addEntity(entity);
125 | }
126 |
127 | FeedMessage differentialFeed = feed.build();
128 | for (GtfsRealtimeIncrementalListener listener : _listeners) {
129 | listener.handleFeed(differentialFeed);
130 | }
131 | _incrementalIndex++;
132 | }
133 |
134 | /****
135 | * {@link GtfsRealtimeSource} Interface
136 | ****/
137 |
138 | @Override
139 | public synchronized FeedMessage getFeed() {
140 | if (_cachedFeed == null) {
141 | FeedHeader.Builder header = FeedHeader.newBuilder();
142 | if (_header != null) {
143 | header.mergeFrom(_header);
144 | }
145 | header.setIncrementality(Incrementality.FULL_DATASET);
146 | header.setTimestamp(System.currentTimeMillis() / 1000);
147 | header.setGtfsRealtimeVersion(GtfsRealtimeConstants.VERSION);
148 |
149 | setIncrementalIndex(header, _incrementalIndex - 1);
150 |
151 | FeedMessage.Builder feed = FeedMessage.newBuilder();
152 | feed.setHeader(header);
153 | feed.addAllEntity(_feedEntities.values());
154 | _cachedFeed = feed.build();
155 | }
156 | return _cachedFeed;
157 | }
158 |
159 | @Override
160 | public void addIncrementalListener(GtfsRealtimeIncrementalListener listener) {
161 | _listeners.add(listener);
162 | listener.handleFeed(getFeed());
163 | }
164 |
165 | @Override
166 | public void removeIncrementalListener(GtfsRealtimeIncrementalListener listener) {
167 | _listeners.remove(listener);
168 | }
169 |
170 | /***
171 | * Private Methods
172 | ****/
173 |
174 | private FeedHeader createIncrementalHeader() {
175 | FeedHeader.Builder header = FeedHeader.newBuilder();
176 | if (_header != null) {
177 | header.mergeFrom(_header);
178 | }
179 | header.setIncrementality(Incrementality.DIFFERENTIAL);
180 | header.setTimestamp(System.currentTimeMillis() / 1000);
181 | header.setGtfsRealtimeVersion(GtfsRealtimeConstants.VERSION);
182 |
183 | setIncrementalIndex(header, _incrementalIndex);
184 |
185 | return header.build();
186 | }
187 |
188 | private void setIncrementalIndex(FeedHeader.Builder header,
189 | long incrementalIndex) {
190 | OneBusAwayFeedHeader.Builder obaHeader = OneBusAwayFeedHeader.newBuilder();
191 | obaHeader.setIncrementalIndex(incrementalIndex);
192 | obaHeader.setIncrementalHeartbeatInterval(_incrementalHeartbeatInterval);
193 | header.setExtension(GtfsRealtimeOneBusAway.obaFeedHeader, obaHeader.build());
194 | }
195 | }
196 |
--------------------------------------------------------------------------------
/src/main/java/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeExporterModule.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2012 Google, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.onebusaway.gtfs_realtime.exporter;
17 |
18 | import java.util.Properties;
19 | import java.util.Set;
20 | import java.util.concurrent.Executors;
21 | import java.util.concurrent.ScheduledExecutorService;
22 |
23 | import org.onebusaway.gtfs_realtime.exporter.GtfsRealtimeExporter.AlertsExporter;
24 | import org.onebusaway.gtfs_realtime.exporter.GtfsRealtimeExporter.MixedFeedExporter;
25 | import org.onebusaway.gtfs_realtime.exporter.GtfsRealtimeExporter.TripUpdatesExporter;
26 | import org.onebusaway.gtfs_realtime.exporter.GtfsRealtimeExporter.VehiclePositionsExporter;
27 | import org.onebusaway.gtfs_realtime.exporter.GtfsRealtimeGuiceBindingTypes.Alerts;
28 | import org.onebusaway.gtfs_realtime.exporter.GtfsRealtimeGuiceBindingTypes.MixedFeed;
29 | import org.onebusaway.gtfs_realtime.exporter.GtfsRealtimeGuiceBindingTypes.TripUpdates;
30 | import org.onebusaway.gtfs_realtime.exporter.GtfsRealtimeGuiceBindingTypes.VehiclePositions;
31 | import org.onebusaway.guice.jetty_exporter.JettyExporterModule;
32 |
33 | import com.google.inject.AbstractModule;
34 | import com.google.inject.Module;
35 | import com.google.inject.Singleton;
36 | import com.google.inject.name.Names;
37 |
38 | /**
39 | * Provides Guice support for wiring up the services provided by the
40 | * onebusaway-gtfs-realtime-exporter library, along with all its dependencies.
41 | *
42 | * Here's a quick example of the library in use:
43 | *
44 | *
45 | * {@code
46 | * Set modules = new HashSet();
47 | * GtfsRealtimeExporterModule.addModuleAndDependencies(modules);
48 | * Injector injector = Guice.createInjector(modules);
49 | * }
50 | *
51 | *
52 | * @author bdferris
53 | */
54 | public class GtfsRealtimeExporterModule extends AbstractModule {
55 |
56 | public static final String NAME_EXECUTOR = "org.onebusway.gtfs_realtime.exporter.GtfsRealtimeExporterModule.executor";
57 |
58 | /**
59 | * Adds a {@link GtfsRealtimeExporterModule} instance to the specified set of
60 | * modules, along with all its dependencies.
61 | *
62 | * @param modules the resulting set of Guice modules
63 | */
64 | public static void addModuleAndDependencies(Set modules) {
65 | modules.add(new GtfsRealtimeExporterModule());
66 | JettyExporterModule.addModuleAndDependencies(modules);
67 | }
68 |
69 | /**
70 | * See {@link GtfsRealtimeExporter} for a discussion of the somewhat
71 | * convoluted binding scheme used here.
72 | */
73 | @Override
74 | protected void configure() {
75 |
76 | bind(GtfsRealtimeSink.class).annotatedWith(Alerts.class).to(
77 | AlertsExporter.class);
78 | bind(GtfsRealtimeSource.class).annotatedWith(Alerts.class).to(
79 | AlertsExporter.class);
80 | bind(GtfsRealtimeExporter.class).annotatedWith(Alerts.class).to(
81 | AlertsExporter.class);
82 | bind(AlertsExporter.class).to(GtfsRealtimeExporterImpl.class).in(
83 | Singleton.class);
84 |
85 | bind(GtfsRealtimeSink.class).annotatedWith(TripUpdates.class).to(
86 | TripUpdatesExporter.class);
87 | bind(GtfsRealtimeSource.class).annotatedWith(TripUpdates.class).to(
88 | TripUpdatesExporter.class);
89 | bind(GtfsRealtimeExporter.class).annotatedWith(TripUpdates.class).to(
90 | TripUpdatesExporter.class);
91 | bind(TripUpdatesExporter.class).to(GtfsRealtimeExporterImpl.class).in(
92 | Singleton.class);
93 |
94 | bind(GtfsRealtimeSink.class).annotatedWith(VehiclePositions.class).to(
95 | VehiclePositionsExporter.class);
96 | bind(GtfsRealtimeSource.class).annotatedWith(VehiclePositions.class).to(
97 | VehiclePositionsExporter.class);
98 | bind(GtfsRealtimeExporter.class).annotatedWith(VehiclePositions.class).to(
99 | VehiclePositionsExporter.class);
100 | bind(VehiclePositionsExporter.class).to(GtfsRealtimeExporterImpl.class).in(
101 | Singleton.class);
102 |
103 | bind(GtfsRealtimeSink.class).annotatedWith(MixedFeed.class).to(
104 | MixedFeedExporter.class);
105 | bind(GtfsRealtimeSource.class).annotatedWith(MixedFeed.class).to(
106 | MixedFeedExporter.class);
107 | bind(MixedFeedExporter.class).to(GtfsRealtimeExporterImpl.class).in(
108 | Singleton.class);
109 |
110 | bind(ScheduledExecutorService.class).annotatedWith(
111 | Names.named(NAME_EXECUTOR)).toInstance(
112 | Executors.newSingleThreadScheduledExecutor());
113 |
114 | String expire = System.getProperty("cache.expire.secs", "0");
115 | bindConstant().annotatedWith(Names.named("cache.expire.secs")).to(expire);
116 | }
117 |
118 | /**
119 | * Implement hashCode() and equals() such that two instances of the module
120 | * will be equal.
121 | */
122 | @Override
123 | public int hashCode() {
124 | return this.getClass().hashCode();
125 | }
126 |
127 | @Override
128 | public boolean equals(Object o) {
129 | if (this == o)
130 | return true;
131 | if (o == null)
132 | return false;
133 | return this.getClass().equals(o.getClass());
134 | }
135 | }
136 |
--------------------------------------------------------------------------------
/src/main/java/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeFileWriter.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2012 Google, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.onebusaway.gtfs_realtime.exporter;
17 |
18 | import java.io.BufferedOutputStream;
19 | import java.io.File;
20 | import java.io.FileOutputStream;
21 | import java.io.IOException;
22 | import java.io.OutputStream;
23 | import java.util.concurrent.ScheduledExecutorService;
24 | import java.util.concurrent.ScheduledFuture;
25 | import java.util.concurrent.TimeUnit;
26 |
27 | import javax.annotation.PostConstruct;
28 | import javax.annotation.PreDestroy;
29 | import javax.inject.Inject;
30 | import javax.inject.Named;
31 |
32 | import org.slf4j.Logger;
33 | import org.slf4j.LoggerFactory;
34 |
35 | import com.google.protobuf.Message;
36 |
37 | /**
38 | * Provides functionality for periodically writing a GTFS-realtime feed to an
39 | * output file.
40 | *
41 | * @author bdferris
42 | *
43 | */
44 | public class GtfsRealtimeFileWriter {
45 |
46 | private static final Logger _log = LoggerFactory.getLogger(GtfsRealtimeFileWriter.class);
47 |
48 | protected GtfsRealtimeSource _source;
49 |
50 | private ScheduledExecutorService _executor;
51 |
52 | private File _path;
53 |
54 | private int _period = 5;
55 |
56 | private ScheduledFuture> _task;
57 |
58 | public void setSource(GtfsRealtimeSource source) {
59 | _source = source;
60 | }
61 |
62 | @Inject
63 | public void setExecutor(@Named(GtfsRealtimeExporterModule.NAME_EXECUTOR)
64 | ScheduledExecutorService executor) {
65 | _executor = executor;
66 | }
67 |
68 | /**
69 | * @param path the output path where the feed will be written
70 | */
71 | public void setPath(File path) {
72 | _path = path;
73 | }
74 |
75 | public int getPeriod() {
76 | return _period;
77 | }
78 |
79 | public void setPeriod(int timeInSeconds) {
80 | _period = timeInSeconds;
81 | }
82 |
83 | @PostConstruct
84 | public void start() {
85 | _task = _executor.scheduleAtFixedRate(new TaskEntryPoint(), 0, _period,
86 | TimeUnit.SECONDS);
87 | }
88 |
89 | @PreDestroy
90 | public void stop() {
91 | if (_task != null) {
92 | _task.cancel(false);
93 | _task = null;
94 | }
95 | }
96 |
97 | protected void writeMessageToFile() throws IOException {
98 | Message message = _source.getFeed();
99 | OutputStream out = new BufferedOutputStream(new FileOutputStream(_path));
100 | message.writeTo(out);
101 | out.close();
102 | }
103 |
104 | private class TaskEntryPoint implements Runnable {
105 |
106 | @Override
107 | public void run() {
108 | try {
109 | writeMessageToFile();
110 | } catch (IOException ex) {
111 | _log.error("Error writing message to output file: " + _path, ex);
112 | }
113 | }
114 | }
115 |
116 | }
117 |
--------------------------------------------------------------------------------
/src/main/java/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeFullUpdate.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013 Google, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.onebusaway.gtfs_realtime.exporter;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 | import com.google.transit.realtime.GtfsRealtime.FeedEntity;
22 |
23 | /**
24 | * Specifies a full update to a GTFS-realtime feed, such that all previous
25 | * entities are replaced by the new entities contained in the update.
26 | *
27 | * @author bdferris
28 | * @see GtfsRealtimeIncrementalUpdate
29 | * @see GtfsRealtimeSink
30 | */
31 | public class GtfsRealtimeFullUpdate {
32 |
33 | private List entities = new ArrayList();
34 |
35 | public void addEntity(FeedEntity entity) {
36 | entities.add(entity);
37 | }
38 |
39 | public List getEntities() {
40 | return entities;
41 | }
42 | }
43 |
--------------------------------------------------------------------------------
/src/main/java/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeGuiceBindingTypes.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013 Google, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.onebusaway.gtfs_realtime.exporter;
17 |
18 | import static java.lang.annotation.ElementType.FIELD;
19 | import static java.lang.annotation.ElementType.METHOD;
20 | import static java.lang.annotation.ElementType.PARAMETER;
21 | import static java.lang.annotation.RetentionPolicy.RUNTIME;
22 |
23 | import java.lang.annotation.Retention;
24 | import java.lang.annotation.Target;
25 |
26 | import com.google.inject.BindingAnnotation;
27 |
28 | /**
29 | * We use Guice for wiring up dependices in the
30 | * onebusaway-gtfs-realtime-exporter library. For most clients, that means
31 | * injecting instances of {@link GtfsRealtimeSink}, {@link GtfsRealtimeSource},
32 | * and {@link GtfsRealtimeExporter}. Typically, clients may produce different
33 | * combinations of GTFS-realtime feeds: alerts, trip-updates, and
34 | * vehicles-positions, each modeled with a separate feed. Each feed type needs
35 | * shared instances of {@link GtfsRealtimeSink} or {@link GtfsRealtimeSource}
36 | * specific to that feed type.
37 | *
38 | * To achieve this, we use Guice Binding Annotations to indicate which type of
39 | * feed we'd like. For example, for a {@link GtfsRealtimeSink} for trip-updates,
40 | * we'd use the following:
41 | *
42 | *
43 | * {@literal @}Inject
44 | * void setTripUpdatesSink({@literal @}TripUpdates GtfsRealtimeSink tripUpdatesSink) {
45 | * ...
46 | * }
47 | *
48 | *
49 | * Note the use of the {@link TripUpdates} annotation on the injection
50 | * parameter. Any instance of {@link GtfsRealtimeSink},
51 | * {@link GtfsRealtimeSource}, or {@link GtfsRealtimeExporter} that is annotated
52 | * with the {@link TripUpdates} annotation will receive the same underlying
53 | * instance. We provide annotations for {@link Alerts}, {@link VehiclePositions}
54 | * , and finally a {@link MixedFeed} if you wish to mix different GTFS-realtime
55 | * feed entity types in the same feed.
56 | *
57 | * Note: one limitation of this approach is that it's tricky to exporter
58 | * multiple different feeds of the same type using the library (eg. two
59 | * trip-updates feeds).
60 | *
61 | * @author bdferris
62 | */
63 | public class GtfsRealtimeGuiceBindingTypes {
64 |
65 | /**
66 | * Annotation to indicate that a wired instance of {@link GtfsRealtimeSink},
67 | * {@link GtfsRealtimeSource}, or {@link GtfsRealtimeExporter} must support
68 | * trip updates data.
69 | *
70 | * @author bdferris
71 | * @see GtfsRealtimeGuiceBindingTypes
72 | */
73 | @BindingAnnotation
74 | @Target({FIELD, PARAMETER, METHOD})
75 | @Retention(RUNTIME)
76 | public @interface TripUpdates {
77 | }
78 |
79 | /**
80 | * Annotation to indicate that a wired instance of {@link GtfsRealtimeSink},
81 | * {@link GtfsRealtimeSource}, or {@link GtfsRealtimeExporter} must support
82 | * vehicle positions data.
83 | *
84 | * @author bdferris
85 | * @see GtfsRealtimeGuiceBindingTypes
86 | */
87 | @BindingAnnotation
88 | @Target({FIELD, PARAMETER, METHOD})
89 | @Retention(RUNTIME)
90 | public @interface VehiclePositions {
91 | }
92 |
93 | /**
94 | * Annotation to indicate that a wired instance of {@link GtfsRealtimeSink},
95 | * {@link GtfsRealtimeSource}, or {@link GtfsRealtimeExporter} must support
96 | * alerts data.
97 | *
98 | * @author bdferris
99 | * @see GtfsRealtimeGuiceBindingTypes
100 | */
101 | @BindingAnnotation
102 | @Target({FIELD, PARAMETER, METHOD})
103 | @Retention(RUNTIME)
104 | public @interface Alerts {
105 | }
106 |
107 | /**
108 | * Annotation to indicate that a wired instance of {@link GtfsRealtimeSink},
109 | * {@link GtfsRealtimeSource}, or {@link GtfsRealtimeExporter} must support a
110 | * mixture of trip updates, vehicle positions, and alerts data.
111 | *
112 | * @author bdferris
113 | * @see GtfsRealtimeGuiceBindingTypes
114 | */
115 | @BindingAnnotation
116 | @Target({FIELD, PARAMETER, METHOD})
117 | @Retention(RUNTIME)
118 | public @interface MixedFeed {
119 | }
120 | }
121 |
--------------------------------------------------------------------------------
/src/main/java/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeIncrementalListener.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013 Google, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.onebusaway.gtfs_realtime.exporter;
17 |
18 | import com.google.transit.realtime.GtfsRealtime.FeedMessage;
19 | import com.google.transit.realtime.GtfsRealtime.FeedHeader.Incrementality;
20 |
21 | /**
22 | * Defines an interface for receiving incremental GTFS-realtime updates.
23 | *
24 | * @author bdferris
25 | *
26 | */
27 | public interface GtfsRealtimeIncrementalListener {
28 |
29 | /**
30 | * Handle a feed update. Updates can either be full datasets (as indicated by
31 | * a {@link Incrementality#FULL_DATASET} value in the header) or incremental
32 | * updates to the most-recent full dataset (as indicated by a
33 | * {@link Incrementality#DIFFERENTIAL} value in the header).
34 | *
35 | * @param feed the feed update
36 | */
37 | public void handleFeed(FeedMessage feed);
38 | }
39 |
--------------------------------------------------------------------------------
/src/main/java/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeIncrementalUpdate.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013 Google, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.onebusaway.gtfs_realtime.exporter;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 | import com.google.transit.realtime.GtfsRealtime.FeedEntity;
22 |
23 | /**
24 | * Specifies an incremental update to a GTFS-realtime feed, such that new feed
25 | * entities will selectively replace any previously updated feed entities. It is
26 | * important to use stable {@link FeedEntity#getId()} id values across updates
27 | * in order for incremental updates to function properly.
28 | *
29 | * @author bdferris
30 | * @see GtfsRealtimeFullUpdate
31 | * @see GtfsRealtimeSink
32 | */
33 | public class GtfsRealtimeIncrementalUpdate {
34 |
35 | private List updatedEntities = new ArrayList();
36 |
37 | private List deletedEntities = new ArrayList();
38 |
39 | private long expirationTime = -1;
40 |
41 | public void addUpdatedEntity(FeedEntity entity) {
42 | updatedEntities.add(entity);
43 | }
44 |
45 | public List getUpdatedEntities() {
46 | return updatedEntities;
47 | }
48 |
49 | public void addDeletedEntity(String entityId) {
50 | deletedEntities.add(entityId);
51 | }
52 |
53 | public List getDeletedEntities() {
54 | return deletedEntities;
55 | }
56 |
57 | public long getExpirationTime() {
58 | return expirationTime;
59 | }
60 |
61 | public void setExpirationTime(long expirationTimeInMilliseconds) {
62 | this.expirationTime = expirationTimeInMilliseconds;
63 | }
64 |
65 | }
66 |
--------------------------------------------------------------------------------
/src/main/java/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeLibrary.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2012 Google, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.onebusaway.gtfs_realtime.exporter;
17 |
18 | import com.google.transit.realtime.GtfsRealtime.TranslatedString;
19 | import com.google.transit.realtime.GtfsRealtime.TranslatedString.Translation;
20 | import com.google.transit.realtime.GtfsRealtimeConstants;
21 | import com.google.transit.realtime.GtfsRealtime.FeedHeader;
22 | import com.google.transit.realtime.GtfsRealtime.FeedMessage;
23 | import com.google.transit.realtime.GtfsRealtime.FeedHeader.Incrementality;
24 |
25 | /**
26 | * Provides a number of convenience methods for working with GTFS-realtime data.
27 | *
28 | * @author bdferris
29 | *
30 | */
31 | public class GtfsRealtimeLibrary {
32 |
33 | /**
34 | * Constructs a new {@link FeedMessage.Builder} that can be used to build a
35 | * new GTFS-realtime feed message. The {@link FeedHeader} will already be
36 | * filled in as a {@link Incrementality#FULL_DATASET} and the timestamp of the
37 | * feed will be set to NOW. This is the minimal requirement for an empty feed
38 | * so the feed could be returned 'as-is' at this point.
39 | *
40 | *
41 | * @return a new feed message builder with a header already populated
42 | */
43 | public static FeedMessage.Builder createFeedMessageBuilder() {
44 | long now = System.currentTimeMillis();
45 | FeedHeader.Builder header = FeedHeader.newBuilder();
46 | header.setTimestamp(now / 1000);
47 | header.setIncrementality(Incrementality.FULL_DATASET);
48 | header.setGtfsRealtimeVersion(GtfsRealtimeConstants.VERSION);
49 | FeedMessage.Builder feedMessageBuilder = FeedMessage.newBuilder();
50 | feedMessageBuilder.setHeader(header);
51 | return feedMessageBuilder;
52 | }
53 |
54 | /**
55 | * @param text the text to include in the translated string
56 | * @return a new {@link TranslatedString} with just a single translation in
57 | * the default language of the feed using the specified text.
58 | */
59 | public static TranslatedString getTextAsTranslatedString(String text) {
60 | TranslatedString.Builder builder = TranslatedString.newBuilder();
61 | Translation.Builder translation = Translation.newBuilder();
62 | translation.setText(text);
63 | builder.addTranslation(translation);
64 | return builder.build();
65 | }
66 | }
67 |
--------------------------------------------------------------------------------
/src/main/java/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeServlet.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2011 Google, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.onebusaway.gtfs_realtime.exporter;
17 |
18 | import java.io.ByteArrayOutputStream;
19 | import java.io.IOException;
20 | import java.net.URL;
21 | import java.nio.ByteBuffer;
22 |
23 | import javax.servlet.Servlet;
24 | import javax.servlet.ServletException;
25 | import javax.servlet.http.HttpServlet;
26 | import javax.servlet.http.HttpServletRequest;
27 | import javax.servlet.http.HttpServletResponse;
28 |
29 | import org.eclipse.jetty.websocket.api.RemoteEndpoint;
30 | import org.eclipse.jetty.websocket.api.Session;
31 | import org.eclipse.jetty.websocket.api.UpgradeRequest;
32 | import org.eclipse.jetty.websocket.api.UpgradeResponse;
33 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketClose;
34 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketConnect;
35 | import org.eclipse.jetty.websocket.api.annotations.WebSocket;
36 | import org.eclipse.jetty.websocket.servlet.WebSocketCreator;
37 | import org.eclipse.jetty.websocket.servlet.WebSocketServlet;
38 | import org.eclipse.jetty.websocket.servlet.WebSocketServletFactory;
39 | import org.onebusaway.guice.jetty_exporter.ServletSource;
40 | import org.slf4j.Logger;
41 | import org.slf4j.LoggerFactory;
42 |
43 | import com.google.protobuf.Message;
44 | import com.google.transit.realtime.GtfsRealtime.FeedMessage;
45 |
46 | /**
47 | * Provides functionality to export a GTFS-realtime feed via HTTP, with support
48 | * for both traditional HTTP GET requests and also incremental requests via
49 | * WebSockets.
50 | *
51 | * @author bdferris
52 | */
53 | public class GtfsRealtimeServlet extends WebSocketServlet implements
54 | ServletSource {
55 |
56 | private static final long serialVersionUID = 1L;
57 |
58 | private static final String CONTENT_TYPE = "application/x-google-protobuf";
59 |
60 | private static final Logger _log = LoggerFactory.getLogger(GtfsRealtimeServlet.class);
61 |
62 | protected GtfsRealtimeSource _source;
63 |
64 | private URL _url;
65 |
66 | public void setSource(GtfsRealtimeSource source) {
67 | _source = source;
68 | }
69 |
70 | public void setUrl(URL url) {
71 | _url = url;
72 | }
73 |
74 | /****
75 | * {@link WebSocketServlet} Interface
76 | ****/
77 |
78 | @Override
79 | public void configure(WebSocketServletFactory factory) {
80 | factory.setCreator(new WebsocketCreatorImpl());
81 | }
82 |
83 | /****
84 | * {@link HttpServlet} Interface
85 | ****/
86 |
87 | @Override
88 | protected void doGet(HttpServletRequest req, HttpServletResponse resp)
89 | throws ServletException, IOException {
90 | boolean debug = req.getParameter("debug") != null;
91 | Message message = _source.getFeed();
92 | if (debug) {
93 | resp.getWriter().print(message);
94 | } else {
95 | resp.setContentType(CONTENT_TYPE);
96 | message.writeTo(resp.getOutputStream());
97 | }
98 | }
99 |
100 | /****
101 | * {@link ServletSource} Interface
102 | ****/
103 |
104 | @Override
105 | public URL getUrl() {
106 | return _url;
107 | }
108 |
109 | @Override
110 | public Servlet getServlet() {
111 | return this;
112 | }
113 |
114 | /****
115 | * Protected Methods
116 | ****/
117 |
118 | class WebsocketCreatorImpl implements WebSocketCreator {
119 | @Override
120 | public Object createWebSocket(UpgradeRequest req, UpgradeResponse resp) {
121 | return new DataWebSocket();
122 | }
123 |
124 | }
125 |
126 | @WebSocket
127 | public class DataWebSocket implements GtfsRealtimeIncrementalListener {
128 |
129 | private Session _session;
130 |
131 | @OnWebSocketConnect
132 | public void onOpen(Session session) {
133 | _log.info("client connect");
134 | _session = session;
135 | _source.addIncrementalListener(this);
136 | }
137 |
138 | @OnWebSocketClose
139 | public void onClose(Session session, int closeCode, String message) {
140 | _session = null;
141 | _source.removeIncrementalListener(this);
142 | }
143 |
144 | /****
145 | * {@link GtfsRealtimeIncrementalListener} Interface
146 | ****/
147 |
148 | @Override
149 | public void handleFeed(FeedMessage feed) {
150 | byte[] buffer = null;
151 | try {
152 | ByteArrayOutputStream out = new ByteArrayOutputStream();
153 | feed.writeTo(out);
154 | buffer = out.toByteArray();
155 | } catch (IOException ex) {
156 | throw new IllegalStateException(ex);
157 | }
158 |
159 | sendMessage(buffer);
160 | }
161 |
162 | private synchronized void sendMessage(byte[] buffer) {
163 | Session session = _session; // copy handle to remove synch issues
164 | if (session == null || !session.isOpen()) {
165 | return;
166 | }
167 | try {
168 | RemoteEndpoint remote = session.getRemote();
169 | remote.sendBytes(ByteBuffer.wrap(buffer));
170 | } catch (Exception ex) {
171 | // If anything goes wrong, we close the connection.
172 | _log.error("error sending message to remote WebSocket client", ex);
173 | try {
174 | // The @OnWebSocketClose event might have already been trigger during
175 | // our attempt to write, but if not, let's close the connection
176 | // ourselves.
177 | if (session != null) {
178 | // This should automatically trigger an @OnWebSocketClose event.
179 | session.close();
180 | }
181 | } catch (IOException ex2) {
182 | _log.error("error closing remote WebSocket connection", ex2);
183 | }
184 | }
185 | }
186 | }
187 | }
188 |
--------------------------------------------------------------------------------
/src/main/java/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeSink.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013 Google, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.onebusaway.gtfs_realtime.exporter;
17 |
18 | import com.google.transit.realtime.GtfsRealtime.FeedEntity;
19 | import com.google.transit.realtime.GtfsRealtime.FeedHeader;
20 |
21 | public interface GtfsRealtimeSink {
22 |
23 | /**
24 | * The exporter library will automatically fill in necessary
25 | * {@link FeedHeader} fields when exporting a feed. However, you may wish to
26 | * specify some additional fields to be included by default (eg. extensions).
27 | * You may specify a partially constructed {@link FeedHeader} object here that
28 | * will be used as a basis for constructing the actual headers sent to
29 | * clients.
30 | *
31 | * @param header a partially constructed header
32 | */
33 | public void setFeedHeaderDefaults(FeedHeader header);
34 |
35 | /**
36 | * Send a full dataset update. The feed entities in the update will replace
37 | * all previous updates received so far.
38 | *
39 | * @param update
40 | */
41 | public void handleFullUpdate(GtfsRealtimeFullUpdate update);
42 |
43 | /**
44 | * Send an incremental update. The feed entities will selectively replace any
45 | * previously updated feed entities. It is important to use stable
46 | * {@link FeedEntity#getId()} id values across updates in order for
47 | * incremental updates to function properly.
48 | *
49 | * @param update
50 | */
51 | public void handleIncrementalUpdate(GtfsRealtimeIncrementalUpdate update);
52 | }
53 |
--------------------------------------------------------------------------------
/src/main/java/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeSource.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2012 Google, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.onebusaway.gtfs_realtime.exporter;
17 |
18 | import com.google.transit.realtime.GtfsRealtime.FeedMessage;
19 |
20 | /**
21 | * Provides a source of GTFS-realtime data, providing methods to receive a full
22 | * data set view of a feed, along with incremental updates as well.
23 | *
24 | * @author bdferris
25 | * @see GtfsRealtimeIncrementalListener
26 | */
27 | public interface GtfsRealtimeSource {
28 |
29 | /**
30 | * @return the full-dataset view of a GTFS-realtime feed
31 | */
32 | public FeedMessage getFeed();
33 |
34 | /**
35 | * Register a new incremental GTFS-realtime listener.
36 | *
37 | * @param listener
38 | */
39 | public void addIncrementalListener(GtfsRealtimeIncrementalListener listener);
40 |
41 | /**
42 | * Un-register a new incremental GTFS-realtime listener.
43 | *
44 | * @param listener
45 | */
46 | public void removeIncrementalListener(GtfsRealtimeIncrementalListener listener);
47 | }
48 |
--------------------------------------------------------------------------------
/src/site/markdown/index.md.vm:
--------------------------------------------------------------------------------
1 | # OneBusAway GTFS-realtime Exporter
2 |
3 | #set( $H = '#' ) ## When using velocity templating (a .vm file), the ## indicates a Velocity comment, which makes Markdown section headers tricky
4 |
5 | Project documentation for the `onebusaway-gtfs-realtime-exporter`, which provides a Java library for easily sharing
6 | [GTFS-realtime](https://developers.google.com/transit/gtfs-realtime) data via an embedded web-server or by writing the
7 | data to file.
8 |
9 | **Current Version:** ${currentVersion}
10 |
11 | $H$H Getting the Library
12 |
13 | If you are using Maven to manage your project, it's easy to add a dependency for the library. First, add the
14 | [OneBusAway Maven repository](https://github.com/OneBusAway/onebusaway/wiki/Maven-Repository) to your project's `pom.xml`.
15 | Then, add a dependency for the library itself. Note that we also add a dependency on `onebusaway-guice-jsr250`, which we
16 | use for starting / stopping services.
17 |
18 | ~~~
19 |
20 |
21 | org.onebusaway
22 | onebusaway-gtfs-realtime-exporter
23 | ${currentVersion}
24 |
25 |
26 | org.onebusaway
27 | onebusaway-guice-jsr250
28 | ${onebusaway_guice_jsr250_version}
29 |
30 |
31 | ~~~
32 |
33 | $H$H Using the Library
34 |
35 | We make heavy use of [Guice](https://code.google.com/p/google-guice/) for dependency injection and management.
36 |
37 | $H$H Example Code
38 |
39 | First, we introduce a `VehiclePositionsProducer`, which is responsible for actually generating GTFS-realtime data about
40 | transit vehicles. Typically, that data comes from some other, internal data stream. The producer asks that an instance
41 | of [GtfsRealtimeSink](./apidocs/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeSink.html) be injected into the class,
42 | annotating with [@VehiclePositions](./apidocs/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeGuiceBindingTypes.VehiclePositions.html)
43 | to indicate that the GTFS-realtime sink should be for vehicle position data.
44 |
45 | The producer spawns a thread that repeatedly generates incremental vehicle position updates. Specifically, the producer
46 | sends [GtfsRealtimeIncrementalUpdate](./apidocs/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeIncrementalUpdate.html)
47 | objects to the sink every time it wants to send a batch of incremental updates. If you'd rather not send incremental
48 | updates, but instead to perform full feed updates, see [GtfsRealtimeFullUpdate](./apidocs/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeFullUpdate.html)
49 | instead.
50 |
51 | ~~~
52 | @Singleton
53 | public class VehiclePositionsProducer {
54 |
55 | private GtfsRealtimeSink _vehiclePositionsSink;
56 |
57 | @Inject
58 | public void setVehiclePositionsSink(@vehiclePositions GtfsRealtimeSink vehiclePositionsSink) {
59 | _vehiclePositionsSink = vehiclePositionsSink;
60 | }
61 |
62 | @PostConstruct
63 | public void start() {
64 | ScheduledExecutorService executor = Executors.newSingleThreadScheduledExecutor();
65 | executor.submit(new Runnable() {
66 | @Override
67 | public void run() {
68 | runLoop();
69 | }
70 | });
71 | }
72 |
73 | private void runLoop() {
74 | while(true) {
75 | GtfsRealtimeIncrementalUpdate update = new GtfsRealtimeIncrementalUpdate();
76 | FeedEntity.Builder entity = FeedEntity.newBuilder();
77 | entity.setId("v123");
78 | VehiclePosition.Builder vehicle = VehiclePosition.newBuilder();
79 | // ... fill in vehicle position data here ...
80 | entity.setVehicle(vehicle);
81 | update.addUpdatedEntity(entity.build());
82 | _vehiclePositionsSink.handleIncrementalUpdate(update);
83 | }
84 | }
85 | }
86 | ~~~
87 |
88 | Once we have our GTFS-realtime producer, we need to wire it up with other modules and start everything running.
89 | Here, we show the `GtfsRealtimeProducerDemo` class, which is responsible for configuring a Guice Injector
90 | with all the necessary dependencies, wiring up our GTFS-realtime. We then inject a couple of classe instances:
91 |
92 | * [GtfsRealtimeSource](./apidocs/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeSource.html) - a source of
93 | GTFS-realtime data that we'll wire up to various export options. Again, we annotate with [@VehiclePositions](./apidocs/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeGuiceBindingTypes.VehiclePositions.html)
94 | to indicate that we care about vehicle position data.
95 | * [VehiclePositionsProducer] - we inject an instance to make sure it gets created
96 | * LifecycleService - we use this to start up all services annotated with @PostConstruct
97 |
98 | Next, we wire up our GTFS-realtime data source to a [GtfsRealtimeServlet](./apidocs/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeServlet.html)
99 | in order to share the feed via HTTP. We also connect the source to a [GtfsRealtimeFileWriter](./apidocs/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeFileWriter.html)
100 | in order to periodically write the feed to an output file. Finally, we start everything up.
101 |
102 | ~~~
103 | public class GtfsRealtimeProducerDemo {
104 |
105 | private GtfsRealtimeSource _vehiclePositionsSource;
106 | private LifecycleService _lifecycleService;
107 |
108 | @Inject
109 | public void setVehiclePositionsProducer(VehiclePositionsProducer producer) {
110 | // This is just here to make sure VehiclePositionsProducer gets instantiated.
111 | }
112 |
113 | @Inject
114 | public void setVehiclePositionsSource(@VehiclePositions GtfsRealtimeSource vehiclePositionsSource) {
115 | _vehiclePositionsSource = vehiclePositionsSource;
116 | }
117 |
118 | @Inject
119 | public void setLifecycleService(LifecycleService lifecycleService) {
120 | _lifecycleService = lifecycleService;
121 | }
122 |
123 | public void run() {
124 | Set modules = new HashSet();
125 | GtfsRealtimeExporterModule.addModuleAndDependencies(modules);
126 | JSR250Module.addModuleAndDependencies(modules);
127 | Injector injector = Guice.createInjector(modules);
128 | injector.injectMembers(this);
129 |
130 | GtfsRealtimeServlet servlet = injector.getInstance(GtfsRealtimeServlet.class);
131 | servlet.setSource(_vehiclePositionsSource);
132 | servlet.setUrl(new URL("http://localhost:8080/trip-updates"));
133 |
134 | GtfsRealtimeFileWriter fileWriter = injector.getInstance(GtfsRealtimeFileWriter.class);
135 | fileWriter.setSource(_vehiclePositionsSource);
136 | fileWriter.setPath(new File("/tmp/trip-updates.pd"));
137 |
138 | _lifecycleService.start();
139 | }
140 | }
141 | ~~~
142 |
--------------------------------------------------------------------------------
/src/site/markdown/release-notes.md.vm:
--------------------------------------------------------------------------------
1 | # Release Notes
2 |
3 | #set( $H = '#' ) ## When using velocity templating (a .vm file), the ## indicates a Velocity comment, which makes Markdown section headers tricky
4 |
5 | $H$H ${currentVersion}
6 |
7 | * Full Documentation: ${site_base_url}/onebusaway-gtfs-realtime-from-siri-cli/${currentVersion}/
8 |
9 | $H$H 1.1.0
10 |
11 | * Add a listener method to GtfsRealtimeProvider for receiving notification of updates. Also provide a default
12 | implementation of that behavior.
13 |
14 | * Bump onebusaway-gtfs-realtime-api to 1.1.0.
15 |
16 | * Full Documentation: ${site_base_url}/onebusaway-gtfs-realtime-from-siri-cli/1.1.0/
17 |
18 | $H$H 1.0.1
19 |
20 | * Fix a bug with the timestamp value in the GTFS-realtime FeedHeader.
21 | * Full Documentation: ${site_base_url}/onebusaway-gtfs-realtime-from-siri-cli/1.0.1/
22 |
23 | $H$H 1.0.0
24 |
25 | * Initial release
26 | * Full Documentation: ${site_base_url}/onebusaway-gtfs-realtime-from-siri-cli/1.0.0/
--------------------------------------------------------------------------------
/src/site/site.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 |
13 |
14 |
15 |
16 |
--------------------------------------------------------------------------------
/src/test/java/org/onebusaway/gtfs_realtime/exporter/DeadlockTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013 Google, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.onebusaway.gtfs_realtime.exporter;
17 |
18 | import static org.junit.Assert.assertTrue;
19 |
20 | import java.net.MalformedURLException;
21 | import java.net.URI;
22 | import java.net.URISyntaxException;
23 | import java.net.URL;
24 | import java.util.HashSet;
25 | import java.util.Set;
26 | import java.util.concurrent.atomic.AtomicLong;
27 |
28 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
29 | import org.eclipse.jetty.websocket.api.annotations.WebSocket;
30 | import org.eclipse.jetty.websocket.client.WebSocketClient;
31 | import org.junit.After;
32 | import org.junit.BeforeClass;
33 | import org.junit.Test;
34 | import org.onebusaway.gtfs_realtime.exporter.GtfsRealtimeGuiceBindingTypes.VehiclePositions;
35 | import org.onebusaway.guice.jsr250.JSR250Module;
36 | import org.onebusaway.guice.jsr250.LifecycleService;
37 | import org.slf4j.Logger;
38 | import org.slf4j.LoggerFactory;
39 |
40 | import com.google.inject.Guice;
41 | import com.google.inject.Inject;
42 | import com.google.inject.Injector;
43 | import com.google.inject.Module;
44 | import com.google.protobuf.ExtensionRegistry;
45 | import com.google.transit.realtime.GtfsRealtime.FeedEntity;
46 | import com.google.transit.realtime.GtfsRealtime.VehiclePosition;
47 | import com.google.transit.realtime.GtfsRealtimeExtensions;
48 |
49 | public class DeadlockTest {
50 |
51 | private static final Logger _log = LoggerFactory.getLogger(DeadlockTest.class);
52 |
53 | private static ExtensionRegistry _extensionRegistry = ExtensionRegistry.newInstance();
54 |
55 | private GtfsRealtimeExporter _exporter;
56 |
57 | private LifecycleService _lifecycleService = null;
58 |
59 | @BeforeClass
60 | public static void beforeClass() {
61 | GtfsRealtimeExtensions.registerExtensions(_extensionRegistry);
62 | }
63 |
64 | @After
65 | public void after() {
66 | if (_lifecycleService != null) {
67 | _lifecycleService.stop();
68 | }
69 | }
70 |
71 | @Inject
72 | public void setExporter(@VehiclePositions
73 | GtfsRealtimeExporter exporter) {
74 | _exporter = exporter;
75 | }
76 |
77 | @Inject
78 | public void setLifecycleService(LifecycleService lifecycleService) {
79 | _lifecycleService = lifecycleService;
80 | }
81 |
82 | @Test
83 | public void testIncrementalFeed() throws Exception {
84 | Set modules = new HashSet();
85 | GtfsRealtimeExporterModule.addModuleAndDependencies(modules);
86 | JSR250Module.addModuleAndDependencies(modules);
87 | Injector injector = Guice.createInjector(modules);
88 | injector.injectMembers(this);
89 |
90 | GtfsRealtimeServlet servlet = injector.getInstance(GtfsRealtimeServlet.class);
91 | servlet.setSource(_exporter);
92 | servlet.setUrl(getUrl());
93 |
94 | _lifecycleService.start();
95 |
96 | Thread dataSourceThread = new Thread(new DataSource(_exporter));
97 | dataSourceThread.start();
98 |
99 | WebSocketClient client = new WebSocketClient();
100 | DataSink sink = new DataSink();
101 |
102 | for (int i = 0; i < 20; ++i) {
103 | _log.info("connect");
104 | client.start();
105 | client.connect(sink, getWebsocketUri());
106 | for (int j = 0; j < 2; ++j) {
107 | long beforeCount = sink.getMessageCount();
108 | Thread.sleep(1000);
109 | long afterCount = sink.getMessageCount();
110 | _log.info("message count=" + (afterCount - beforeCount));
111 | assertTrue(afterCount - beforeCount > 5);
112 | }
113 | _log.info("disconnect");
114 | client.stop();
115 | }
116 | }
117 |
118 | private static URL getUrl() {
119 | try {
120 | String port = System.getProperty("org_onebusaway_test_port", "8080");
121 | return new URL("http://localhost:" + port + "/my-servlet");
122 | } catch (MalformedURLException ex) {
123 | throw new IllegalStateException(ex);
124 | }
125 | }
126 |
127 | private static URI getWebsocketUri() {
128 | try {
129 | String port = System.getProperty("org_onebusaway_test_port", "8080");
130 | return new URI("ws://localhost:" + port + "/my-servlet");
131 | } catch (URISyntaxException ex) {
132 | throw new IllegalStateException(ex);
133 | }
134 | }
135 |
136 | /**
137 | * Sends incremental feed messages as fast as it can.
138 | */
139 | private static class DataSource implements Runnable {
140 |
141 | private GtfsRealtimeSink _sink;
142 |
143 | public DataSource(GtfsRealtimeSink sink) {
144 | _sink = sink;
145 | }
146 |
147 | @Override
148 | public void run() {
149 | FeedEntity.Builder entity = FeedEntity.newBuilder();
150 | entity.setId("tacos");
151 | VehiclePosition.Builder position = VehiclePosition.newBuilder();
152 | entity.setVehicle(position);
153 | final FeedEntity feedEntity = entity.build();
154 |
155 | while (!Thread.interrupted()) {
156 | GtfsRealtimeIncrementalUpdate update = new GtfsRealtimeIncrementalUpdate();
157 | update.addUpdatedEntity(feedEntity);
158 | _sink.handleIncrementalUpdate(update);
159 | }
160 | }
161 | }
162 |
163 | /**
164 | * Counts messages received from a GTFS-realtime incremental feed WebSocket.
165 | */
166 | @WebSocket
167 | public static class DataSink {
168 |
169 | private AtomicLong _messageCount = new AtomicLong();
170 |
171 | public long getMessageCount() {
172 | return _messageCount.get();
173 | }
174 |
175 | @OnWebSocketMessage
176 | public void onMessage(byte[] data, int offset, int length) {
177 | _messageCount.incrementAndGet();
178 | }
179 | }
180 | }
181 |
--------------------------------------------------------------------------------
/src/test/java/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeExporterImplTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013 Google, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.onebusaway.gtfs_realtime.exporter;
17 |
18 | import static org.junit.Assert.*;
19 |
20 | import org.junit.Before;
21 | import org.junit.Test;
22 |
23 | import com.google.transit.realtime.GtfsRealtimeOneBusAway;
24 | import com.google.transit.realtime.GtfsRealtime.FeedEntity;
25 | import com.google.transit.realtime.GtfsRealtime.FeedMessage;
26 | import com.google.transit.realtime.GtfsRealtime.FeedHeader.Incrementality;
27 | import com.google.transit.realtime.GtfsRealtimeOneBusAway.OneBusAwayFeedHeader;
28 |
29 | public class GtfsRealtimeExporterImplTest {
30 |
31 | private GtfsRealtimeExporterImpl _exporter;
32 |
33 | private ListenerImpl _listener;
34 |
35 | @Before
36 | public void setup() {
37 | _exporter = new GtfsRealtimeExporterImpl();
38 | _listener = new ListenerImpl();
39 | }
40 |
41 | @Test
42 | public void testFullUpdate() {
43 | GtfsRealtimeFullUpdate update = new GtfsRealtimeFullUpdate();
44 | FeedEntity.Builder feedEntity = FeedEntity.newBuilder();
45 | feedEntity.setId("v123");
46 | update.addEntity(feedEntity.build());
47 |
48 | _exporter.handleFullUpdate(update);
49 |
50 | FeedMessage feed = _exporter.getFeed();
51 | assertEquals(Incrementality.FULL_DATASET,
52 | feed.getHeader().getIncrementality());
53 | OneBusAwayFeedHeader obaHeader = feed.getHeader().getExtension(
54 | GtfsRealtimeOneBusAway.obaFeedHeader);
55 | assertEquals(1, obaHeader.getIncrementalIndex());
56 | assertEquals(1, feed.getEntityCount());
57 | assertEquals("v123", feed.getEntity(0).getId());
58 |
59 | _exporter.addIncrementalListener(_listener);
60 | assertSame(feed, _listener.getFeed());
61 |
62 | update = new GtfsRealtimeFullUpdate();
63 | feedEntity = FeedEntity.newBuilder();
64 | feedEntity.setId("v456");
65 | update.addEntity(feedEntity.build());
66 |
67 | _exporter.handleFullUpdate(update);
68 |
69 | feed = _exporter.getFeed();
70 | obaHeader = feed.getHeader().getExtension(
71 | GtfsRealtimeOneBusAway.obaFeedHeader);
72 | assertEquals(2, obaHeader.getIncrementalIndex());
73 | assertEquals(1, feed.getEntityCount());
74 | assertEquals("v456", feed.getEntity(0).getId());
75 | assertSame(feed, _listener.getFeed());
76 | }
77 |
78 | @Test
79 | public void testIncrementalUpdate() {
80 | {
81 | GtfsRealtimeIncrementalUpdate update = new GtfsRealtimeIncrementalUpdate();
82 | FeedEntity.Builder feedEntity = FeedEntity.newBuilder();
83 | feedEntity.setId("v123");
84 | update.addUpdatedEntity(feedEntity.build());
85 |
86 | _exporter.handleIncrementalUpdate(update);
87 | }
88 |
89 | {
90 | FeedMessage feed = _exporter.getFeed();
91 | assertEquals(Incrementality.FULL_DATASET,
92 | feed.getHeader().getIncrementality());
93 | OneBusAwayFeedHeader obaHeader = feed.getHeader().getExtension(
94 | GtfsRealtimeOneBusAway.obaFeedHeader);
95 | assertEquals(1, obaHeader.getIncrementalIndex());
96 | assertEquals(1, feed.getEntityCount());
97 | assertEquals("v123", feed.getEntity(0).getId());
98 | }
99 |
100 | {
101 | GtfsRealtimeIncrementalUpdate update = new GtfsRealtimeIncrementalUpdate();
102 | FeedEntity.Builder feedEntity = FeedEntity.newBuilder();
103 | feedEntity.setId("v456");
104 | update.addUpdatedEntity(feedEntity.build());
105 | update.addDeletedEntity("v123");
106 |
107 | _exporter.handleIncrementalUpdate(update);
108 | }
109 |
110 | {
111 | FeedMessage feed = _exporter.getFeed();
112 | OneBusAwayFeedHeader obaHeader = feed.getHeader().getExtension(
113 | GtfsRealtimeOneBusAway.obaFeedHeader);
114 | assertEquals(2, obaHeader.getIncrementalIndex());
115 | assertEquals(1, feed.getEntityCount());
116 | assertEquals("v456", feed.getEntity(0).getId());
117 | }
118 |
119 | _exporter.addIncrementalListener(_listener);
120 |
121 | assertSame(_exporter.getFeed(), _listener.getFeed());
122 |
123 | {
124 | GtfsRealtimeIncrementalUpdate update = new GtfsRealtimeIncrementalUpdate();
125 | FeedEntity.Builder feedEntity = FeedEntity.newBuilder();
126 | feedEntity.setId("v789");
127 | update.addUpdatedEntity(feedEntity.build());
128 | update.addDeletedEntity("v456");
129 |
130 | _exporter.handleIncrementalUpdate(update);
131 | }
132 |
133 | {
134 | FeedMessage feed = _listener.getFeed();
135 | assertEquals(Incrementality.DIFFERENTIAL,
136 | feed.getHeader().getIncrementality());
137 | OneBusAwayFeedHeader obaHeader = feed.getHeader().getExtension(
138 | GtfsRealtimeOneBusAway.obaFeedHeader);
139 | assertEquals(3, obaHeader.getIncrementalIndex());
140 | assertEquals(2, feed.getEntityCount());
141 | assertEquals("v789", feed.getEntity(0).getId());
142 | assertEquals("v456", feed.getEntity(1).getId());
143 | assertTrue(feed.getEntity(1).getIsDeleted());
144 | }
145 | }
146 |
147 | private static class ListenerImpl implements GtfsRealtimeIncrementalListener {
148 |
149 | private FeedMessage _feed;
150 |
151 | public FeedMessage getFeed() {
152 | return _feed;
153 | }
154 |
155 | @Override
156 | public void handleFeed(FeedMessage feed) {
157 | _feed = feed;
158 | }
159 | }
160 | }
161 |
--------------------------------------------------------------------------------
/src/test/java/org/onebusaway/gtfs_realtime/exporter/GtfsRealtimeFileWriterTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013 Google, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.onebusaway.gtfs_realtime.exporter;
17 |
18 | import static org.junit.Assert.assertEquals;
19 | import static org.junit.Assert.assertTrue;
20 |
21 | import java.io.File;
22 | import java.io.FileInputStream;
23 | import java.io.IOException;
24 | import java.io.InputStream;
25 | import java.util.concurrent.ScheduledExecutorService;
26 | import java.util.concurrent.TimeUnit;
27 |
28 | import org.junit.Before;
29 | import org.junit.Test;
30 | import org.mockito.ArgumentCaptor;
31 | import org.mockito.Mockito;
32 |
33 | import com.google.transit.realtime.GtfsRealtime.FeedHeader;
34 | import com.google.transit.realtime.GtfsRealtime.FeedHeader.Incrementality;
35 | import com.google.transit.realtime.GtfsRealtime.FeedMessage;
36 | import com.google.transit.realtime.GtfsRealtimeConstants;
37 |
38 | public class GtfsRealtimeFileWriterTest {
39 |
40 | private GtfsRealtimeFileWriter _writer;
41 |
42 | private GtfsRealtimeSource _source;
43 |
44 | private ScheduledExecutorService _executor;
45 |
46 | private File _path;
47 |
48 | @Before
49 | public void setup() throws IOException {
50 | _writer = new GtfsRealtimeFileWriter();
51 |
52 | _source = Mockito.mock(GtfsRealtimeSource.class);
53 | _writer.setSource(_source);
54 |
55 | _executor = Mockito.mock(ScheduledExecutorService.class);
56 | _writer.setExecutor(_executor);
57 |
58 | _path = File.createTempFile(GtfsRealtimeFileWriterTest.class.getName(),
59 | "-FeedMessage.pb");
60 | _path.delete();
61 | _path.deleteOnExit();
62 | _writer.setPath(_path);
63 | }
64 |
65 | @Test
66 | public void test() throws IOException {
67 | ArgumentCaptor captureRunnable = ArgumentCaptor.forClass(Runnable.class);
68 | Mockito.when(
69 | _executor.scheduleAtFixedRate(captureRunnable.capture(),
70 | Mockito.eq(0L), Mockito.eq(5L), Mockito.eq(TimeUnit.SECONDS))).thenReturn(
71 | null);
72 | _writer.start();
73 |
74 | FeedMessage.Builder feed = FeedMessage.newBuilder();
75 | FeedHeader.Builder header = feed.getHeaderBuilder();
76 | header.setIncrementality(Incrementality.FULL_DATASET);
77 | header.setTimestamp(1234L);
78 | header.setGtfsRealtimeVersion(GtfsRealtimeConstants.VERSION);
79 |
80 | Mockito.when(_source.getFeed()).thenReturn(feed.build());
81 |
82 | Runnable writerTask = captureRunnable.getValue();
83 | writerTask.run();
84 |
85 | assertTrue(_path.exists());
86 |
87 | InputStream in = new FileInputStream(_path);
88 | FeedMessage actualFeed = FeedMessage.parseFrom(in);
89 | assertEquals(header.getTimestamp(), actualFeed.getHeader().getTimestamp());
90 | }
91 | }
92 |
--------------------------------------------------------------------------------
/src/test/java/org/onebusaway/gtfs_realtime/exporter/IntegrationTest.java:
--------------------------------------------------------------------------------
1 | /**
2 | * Copyright (C) 2013 Google, Inc.
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 | package org.onebusaway.gtfs_realtime.exporter;
17 |
18 | import static org.junit.Assert.assertEquals;
19 |
20 | import java.io.File;
21 | import java.io.FileInputStream;
22 | import java.io.IOException;
23 | import java.net.MalformedURLException;
24 | import java.net.URI;
25 | import java.net.URISyntaxException;
26 | import java.net.URL;
27 | import java.util.ArrayList;
28 | import java.util.HashSet;
29 | import java.util.List;
30 | import java.util.Set;
31 | import java.util.concurrent.CountDownLatch;
32 | import java.util.concurrent.Future;
33 | import java.util.concurrent.TimeUnit;
34 |
35 | import org.eclipse.jetty.websocket.api.Session;
36 | import org.eclipse.jetty.websocket.api.annotations.OnWebSocketMessage;
37 | import org.eclipse.jetty.websocket.api.annotations.WebSocket;
38 | import org.eclipse.jetty.websocket.client.WebSocketClient;
39 | import org.junit.After;
40 | import org.junit.BeforeClass;
41 | import org.junit.Test;
42 | import org.onebusaway.gtfs_realtime.exporter.GtfsRealtimeGuiceBindingTypes.VehiclePositions;
43 | import org.onebusaway.guice.jsr250.JSR250Module;
44 | import org.onebusaway.guice.jsr250.LifecycleService;
45 |
46 | import com.google.inject.Guice;
47 | import com.google.inject.Inject;
48 | import com.google.inject.Injector;
49 | import com.google.inject.Module;
50 | import com.google.protobuf.ByteString;
51 | import com.google.protobuf.ExtensionRegistry;
52 | import com.google.protobuf.InvalidProtocolBufferException;
53 | import com.google.transit.realtime.GtfsRealtime.FeedEntity;
54 | import com.google.transit.realtime.GtfsRealtime.FeedHeader;
55 | import com.google.transit.realtime.GtfsRealtime.FeedHeader.Incrementality;
56 | import com.google.transit.realtime.GtfsRealtime.FeedMessage;
57 | import com.google.transit.realtime.GtfsRealtime.VehiclePosition;
58 | import com.google.transit.realtime.GtfsRealtimeExtensions;
59 | import com.google.transit.realtime.GtfsRealtimeOneBusAway;
60 | import com.google.transit.realtime.GtfsRealtimeOneBusAway.OneBusAwayFeedHeader;
61 |
62 | public class IntegrationTest {
63 |
64 | private static ExtensionRegistry _extensionRegistry = ExtensionRegistry.newInstance();
65 |
66 | private GtfsRealtimeExporter _exporter;
67 |
68 | private LifecycleService _lifecycleService = null;
69 |
70 | @BeforeClass
71 | public static void beforeClass() {
72 | GtfsRealtimeExtensions.registerExtensions(_extensionRegistry);
73 | }
74 |
75 | @After
76 | public void after() {
77 | if (_lifecycleService != null) {
78 | _lifecycleService.stop();
79 | }
80 | }
81 |
82 | @Inject
83 | public void setExporter(@VehiclePositions
84 | GtfsRealtimeExporter exporter) {
85 | _exporter = exporter;
86 | }
87 |
88 | @Inject
89 | public void setLifecycleService(LifecycleService lifecycleService) {
90 | _lifecycleService = lifecycleService;
91 | }
92 |
93 | @Test
94 | public void testFullFeed() throws IOException, InterruptedException {
95 | URL url = getUrl();
96 |
97 | File path = File.createTempFile(getClass().getName() + "-", ".pb");
98 | path.delete();
99 | path.deleteOnExit();
100 |
101 | Set modules = new HashSet();
102 | GtfsRealtimeExporterModule.addModuleAndDependencies(modules);
103 | JSR250Module.addModuleAndDependencies(modules);
104 | Injector injector = Guice.createInjector(modules);
105 | injector.injectMembers(this);
106 |
107 | GtfsRealtimeServlet servlet = injector.getInstance(GtfsRealtimeServlet.class);
108 | servlet.setSource(_exporter);
109 | servlet.setUrl(url);
110 |
111 | GtfsRealtimeFileWriter fileWriter = injector.getInstance(GtfsRealtimeFileWriter.class);
112 | fileWriter.setSource(_exporter);
113 | fileWriter.setPath(path);
114 |
115 | _lifecycleService.start();
116 |
117 | {
118 | GtfsRealtimeIncrementalUpdate update = new GtfsRealtimeIncrementalUpdate();
119 | FeedEntity.Builder entity = FeedEntity.newBuilder();
120 | entity.setId("tacos");
121 | VehiclePosition.Builder position = VehiclePosition.newBuilder();
122 | entity.setVehicle(position);
123 | update.addUpdatedEntity(entity.build());
124 | _exporter.handleIncrementalUpdate(update);
125 | }
126 |
127 | Thread.sleep((fileWriter.getPeriod() + 1) * 1000);
128 |
129 | FeedMessage feed = FeedMessage.parseFrom(new FileInputStream(path));
130 | assertEquals(1, feed.getEntityCount());
131 | assertEquals("tacos", feed.getEntity(0).getId());
132 |
133 | feed = FeedMessage.parseFrom(url.openStream());
134 | assertEquals(1, feed.getEntityCount());
135 | assertEquals("tacos", feed.getEntity(0).getId());
136 |
137 | {
138 | GtfsRealtimeIncrementalUpdate update = new GtfsRealtimeIncrementalUpdate();
139 | FeedEntity.Builder entity = FeedEntity.newBuilder();
140 | entity.setId("nachos");
141 | VehiclePosition.Builder position = VehiclePosition.newBuilder();
142 | entity.setVehicle(position);
143 | update.addUpdatedEntity(entity.build());
144 | _exporter.handleIncrementalUpdate(update);
145 | }
146 |
147 | Thread.sleep((fileWriter.getPeriod() + 1) * 1000);
148 |
149 | feed = FeedMessage.parseFrom(new FileInputStream(path));
150 | assertEquals(2, feed.getEntityCount());
151 |
152 | feed = FeedMessage.parseFrom(url.openStream());
153 | assertEquals(2, feed.getEntityCount());
154 | }
155 |
156 | @Test
157 | public void testIncrementalFeed() throws Exception {
158 | Set modules = new HashSet();
159 | GtfsRealtimeExporterModule.addModuleAndDependencies(modules);
160 | JSR250Module.addModuleAndDependencies(modules);
161 | Injector injector = Guice.createInjector(modules);
162 | injector.injectMembers(this);
163 |
164 | GtfsRealtimeServlet servlet = injector.getInstance(GtfsRealtimeServlet.class);
165 | servlet.setSource(_exporter);
166 | servlet.setUrl(getUrl());
167 |
168 | _lifecycleService.start();
169 |
170 | {
171 | GtfsRealtimeIncrementalUpdate update = new GtfsRealtimeIncrementalUpdate();
172 | FeedEntity.Builder entity = FeedEntity.newBuilder();
173 | entity.setId("tacos");
174 | VehiclePosition.Builder position = VehiclePosition.newBuilder();
175 | entity.setVehicle(position);
176 | update.addUpdatedEntity(entity.build());
177 | _exporter.handleIncrementalUpdate(update);
178 | }
179 |
180 | WebSocketClient client = new WebSocketClient();
181 | client.start();
182 | GtfsRealtimeSocket socket = new GtfsRealtimeSocket();
183 | Future future = client.connect(socket, getWebsocketUri());
184 | future.get(10, TimeUnit.SECONDS);
185 |
186 | CountDownLatch latch = socket.setLatch(1);
187 | latch.await(2, TimeUnit.SECONDS);
188 |
189 | List feeds = socket.getFeeds();
190 | assertEquals(1, feeds.size());
191 | FeedMessage feed = feeds.get(0);
192 | FeedHeader header = feed.getHeader();
193 | assertEquals(Incrementality.FULL_DATASET, header.getIncrementality());
194 | OneBusAwayFeedHeader obaHeader = header.getExtension(GtfsRealtimeOneBusAway.obaFeedHeader);
195 | long index = obaHeader.getIncrementalIndex();
196 | assertEquals(1, index);
197 | assertEquals(1, feed.getEntityCount());
198 |
199 | latch = socket.setLatch(1);
200 |
201 | {
202 | GtfsRealtimeIncrementalUpdate update = new GtfsRealtimeIncrementalUpdate();
203 | FeedEntity.Builder entity = FeedEntity.newBuilder();
204 | entity.setId("nachos");
205 | VehiclePosition.Builder position = VehiclePosition.newBuilder();
206 | entity.setVehicle(position);
207 | update.addUpdatedEntity(entity.build());
208 | _exporter.handleIncrementalUpdate(update);
209 | }
210 |
211 | latch.await();
212 |
213 | assertEquals(2, feeds.size());
214 | feed = feeds.get(1);
215 | header = feed.getHeader();
216 | assertEquals(Incrementality.DIFFERENTIAL, header.getIncrementality());
217 | obaHeader = header.getExtension(GtfsRealtimeOneBusAway.obaFeedHeader);
218 | assertEquals(2, obaHeader.getIncrementalIndex());
219 | assertEquals(1, feed.getEntityCount());
220 | }
221 |
222 | private static URL getUrl() {
223 | try {
224 | String port = System.getProperty("org_onebusaway_test_port", "8080");
225 | return new URL("http://localhost:" + port + "/my-servlet");
226 | } catch (MalformedURLException ex) {
227 | throw new IllegalStateException(ex);
228 | }
229 | }
230 |
231 | private static URI getWebsocketUri() {
232 | try {
233 | String port = System.getProperty("org_onebusaway_test_port", "8080");
234 | return new URI("ws://localhost:" + port + "/my-servlet");
235 | } catch (URISyntaxException ex) {
236 | throw new IllegalStateException(ex);
237 | }
238 | }
239 |
240 | @WebSocket
241 | public static class GtfsRealtimeSocket {
242 |
243 | private CountDownLatch _latch = null;
244 |
245 | private List _feeds = new ArrayList();
246 |
247 | public List getFeeds() {
248 | return _feeds;
249 | }
250 |
251 | public CountDownLatch setLatch(int count) {
252 | _latch = new CountDownLatch(count);
253 | return _latch;
254 | }
255 |
256 | @OnWebSocketMessage
257 | public void onMessage(byte[] data, int offset, int length) {
258 | try {
259 | FeedMessage feed = FeedMessage.parseFrom(
260 | ByteString.copyFrom(data, offset, length), _extensionRegistry);
261 | _feeds.add(feed);
262 | if (_latch != null) {
263 | _latch.countDown();
264 | }
265 | } catch (InvalidProtocolBufferException ex) {
266 | throw new IllegalStateException(ex);
267 | }
268 | }
269 | }
270 | }
271 |
--------------------------------------------------------------------------------
/src/test/resources/log4j.properties:
--------------------------------------------------------------------------------
1 | log4j.rootLogger = INFO, stderr
2 |
3 | log4j.appender.stderr = org.apache.log4j.ConsoleAppender
4 | # Follow must be set to true, so that when stderr is closed and reopened in daemonization, we'll continue to log
5 | log4j.appender.stderr.Follow = TRUE
6 | log4j.appender.stderr.Threshold = DEBUG
7 | log4j.appender.stderr.Target = System.err
8 | log4j.appender.stderr.layout = org.apache.log4j.PatternLayout
9 | log4j.appender.stderr.layout.ConversionPattern = %d{ISO8601} %-5p [%F:%L] : %m%n
--------------------------------------------------------------------------------