001/*
002 * (C) Copyright 2006-2014 Nuxeo SA (http://nuxeo.com/) and others.
003 *
004 * Licensed under the Apache License, Version 2.0 (the "License");
005 * you may not use this file except in compliance with the License.
006 * You may obtain a copy of the License at
007 *
008 *     http://www.apache.org/licenses/LICENSE-2.0
009 *
010 * Unless required by applicable law or agreed to in writing, software
011 * distributed under the License is distributed on an "AS IS" BASIS,
012 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
013 * See the License for the specific language governing permissions and
014 * limitations under the License.
015 *
016 * Contributors:
017 *     Nuxeo - initial API and implementation
018 *
019 */
020
021package org.nuxeo.connect.client.jsf;
022
023import java.io.IOException;
024import java.io.Serializable;
025import java.util.ArrayList;
026import java.util.Calendar;
027import java.util.List;
028import java.util.Map;
029
030import javax.faces.context.FacesContext;
031import javax.faces.model.SelectItem;
032import javax.servlet.http.HttpServletRequest;
033
034import org.apache.commons.codec.binary.Base64;
035import org.apache.commons.logging.Log;
036import org.apache.commons.logging.LogFactory;
037import org.jboss.seam.ScopeType;
038import org.jboss.seam.annotations.Factory;
039import org.jboss.seam.annotations.In;
040import org.jboss.seam.annotations.Name;
041import org.jboss.seam.annotations.Scope;
042import org.jboss.seam.contexts.Contexts;
043import org.jboss.seam.faces.FacesMessages;
044import org.jboss.seam.international.StatusMessage;
045import org.nuxeo.common.utils.ExceptionUtils;
046import org.nuxeo.connect.client.status.ConnectStatusHolder;
047import org.nuxeo.connect.client.status.ConnectUpdateStatusInfo;
048import org.nuxeo.connect.client.status.SubscriptionStatusWrapper;
049import org.nuxeo.connect.connector.NuxeoClientInstanceType;
050import org.nuxeo.connect.connector.http.ConnectUrlConfig;
051import org.nuxeo.connect.data.SubscriptionStatusType;
052import org.nuxeo.connect.identity.LogicalInstanceIdentifier;
053import org.nuxeo.connect.identity.LogicalInstanceIdentifier.InvalidCLID;
054import org.nuxeo.connect.identity.LogicalInstanceIdentifier.NoCLID;
055import org.nuxeo.connect.identity.TechnicalInstanceIdentifier;
056import org.nuxeo.connect.registration.ConnectRegistrationService;
057import org.nuxeo.connect.update.PackageException;
058import org.nuxeo.connect.update.PackageUpdateService;
059import org.nuxeo.ecm.core.api.Blob;
060import org.nuxeo.ecm.core.api.CloseableFile;
061import org.nuxeo.runtime.api.Framework;
062
063/**
064 * Seam Bean to expose Connect Registration operations.
065 * <ul>
066 * <li>getting status
067 * <li>registering
068 * <li>...
069 * </ul>
070 *
071 * @author <a href="mailto:td@nuxeo.com">Thierry Delprat</a>
072 */
073@Name("connectStatus")
074@Scope(ScopeType.CONVERSATION)
075public class ConnectStatusActionBean implements Serializable {
076
077    private static final long serialVersionUID = 1L;
078
079    public static final String CLIENT_BANNER_TYPE = "clientSideBanner";
080
081    public static final String SERVER_BANNER_TYPE = "serverSideBanner";
082
083    private static final Log log = LogFactory.getLog(ConnectStatusActionBean.class);
084
085    @In(create = true, required = false)
086    protected FacesMessages facesMessages;
087
088    @In(create = true, required = true, value = "appsViews")
089    protected AppCenterViewsManager appsViews;
090
091    @In(create = true)
092    protected Map<String, String> messages;
093
094    protected String CLID;
095
096    protected String token;
097
098    protected ConnectUpdateStatusInfo connectionStatusCache;
099
100    public String getRegistredCLID() throws NoCLID {
101        if (isRegistered()) {
102            return LogicalInstanceIdentifier.instance().getCLID();
103        } else {
104            return null;
105        }
106    }
107
108    public String getCLID() {
109        return CLID;
110    }
111
112    public void setCLID(String cLID) {
113        CLID = cLID;
114    }
115
116    public String unregister() {
117        LogicalInstanceIdentifier.cleanUp();
118        resetRegister();
119        return null;
120    }
121
122    public List<SelectItem> getInstanceTypes() {
123        List<SelectItem> types = new ArrayList<>();
124        for (NuxeoClientInstanceType itype : NuxeoClientInstanceType.values()) {
125            SelectItem item = new SelectItem(itype.getValue(), "label.instancetype." + itype.getValue());
126            types.add(item);
127        }
128        return types;
129    }
130
131    protected ConnectRegistrationService getService() {
132        return Framework.getService(ConnectRegistrationService.class);
133    }
134
135    /**
136     * @since 9.2
137     */
138    @Factory(scope = ScopeType.APPLICATION, value = "registredConnectInstance")
139    public boolean isRegistered() {
140        return getService().isInstanceRegistered();
141    }
142
143    /**
144     * @deprecated Since 9.2, use {@link #isRegistered()} instead.
145     */
146    @Deprecated
147    public boolean isRegistred() {
148        return isRegistered();
149    }
150
151    protected void flushContextCache() {
152        // A4J and Event cache don't play well ...
153        Contexts.getApplicationContext().remove("registredConnectInstance");
154        Contexts.getApplicationContext().remove("connectUpdateStatusInfo");
155        appsViews.flushCache();
156    }
157
158    @Factory(value = "connectServerReachable", scope = ScopeType.EVENT)
159    public boolean isConnectServerReachable() {
160        return !ConnectStatusHolder.instance().getStatus().isConnectServerUnreachable();
161    }
162
163    public String refreshStatus() {
164        ConnectStatusHolder.instance().getStatus(true);
165        flushContextCache();
166        return null;
167    }
168
169    public SubscriptionStatusWrapper getStatus() {
170        return ConnectStatusHolder.instance().getStatus();
171    }
172
173    /**
174     * Returns the registration expiration date included in the CLID, or {@code null} if the CLID cannot be loaded or
175     * doesn't include the expiration date (old v0 format).
176     *
177     * @since 10.2
178     */
179    public Calendar getRegistrationExpirationDate() {
180        long timestamp = ConnectStatusHolder.instance().getRegistrationExpirationTimestamp();
181        if (timestamp > -1) {
182            Calendar calendar = Calendar.getInstance();
183            calendar.setTimeInMillis(timestamp * 1000);
184            return calendar;
185        } else {
186            return null;
187        }
188    }
189
190    public String resetRegister() {
191        flushContextCache();
192        return null;
193    }
194
195    public String getToken() {
196        return token;
197    }
198
199    public void setToken(String token) throws IOException, InvalidCLID {
200        if (token != null) {
201            String tokenData = new String(Base64.decodeBase64(token));
202            String[] tokenDataLines = tokenData.split("\n");
203            for (String line : tokenDataLines) {
204                String[] parts = line.split(":");
205                if (parts.length > 1 && "CLID".equals(parts[0])) {
206                    getService().localRegisterInstance(parts[1], " ");
207                    // force refresh of connect status info
208                    connectionStatusCache = null;
209                    flushContextCache();
210                    ConnectStatusHolder.instance().flush();
211                }
212            }
213        }
214    }
215
216    public String getCTID() {
217        try {
218            return TechnicalInstanceIdentifier.instance().getCTID();
219        } catch (Exception e) { // stupid API
220            throw ExceptionUtils.runtimeException(e);
221        }
222    }
223
224    public String localRegister() {
225        try {
226            getService().localRegisterInstance(CLID, "");
227        } catch (InvalidCLID e) {
228            facesMessages.addToControl("offline_clid", StatusMessage.Severity.WARN,
229                    messages.get("label.connect.wrongCLID"));
230        } catch (IOException e) {
231            facesMessages.addToControl("offline_clid", StatusMessage.Severity.ERROR,
232                    messages.get("label.connect.registrationError"));
233            log.error("Error while registering instance locally", e);
234        }
235        flushContextCache();
236        return null;
237    }
238
239    protected Blob packageToUpload;
240
241    protected String packageFileName;
242
243    public String getPackageFileName() {
244        return packageFileName;
245    }
246
247    public void setPackageFileName(String packageFileName) {
248        this.packageFileName = packageFileName;
249    }
250
251    public Blob getPackageToUpload() {
252        return packageToUpload;
253    }
254
255    public void setPackageToUpload(Blob packageToUpload) {
256        this.packageToUpload = packageToUpload;
257    }
258
259    public void uploadPackage() throws IOException {
260        if (packageToUpload == null) {
261            facesMessages.add(StatusMessage.Severity.WARN, "label.connect.nofile");
262            return;
263        }
264        PackageUpdateService pus = Framework.getService(PackageUpdateService.class);
265        try (CloseableFile cfile = packageToUpload.getCloseableFile()) {
266            pus.addPackage(cfile.getFile());
267        } catch (PackageException e) {
268            log.warn(e, e);
269            facesMessages.add(StatusMessage.Severity.ERROR,
270                    messages.get("label.connect.wrong.package") + ":" + e.getMessage());
271            return;
272        } finally {
273            packageFileName = null;
274            packageToUpload = null;
275        }
276    }
277
278    public ConnectUpdateStatusInfo getDynamicConnectUpdateStatusInfo() {
279        HttpServletRequest req = (HttpServletRequest) FacesContext.getCurrentInstance()
280                                                                  .getExternalContext()
281                                                                  .getRequest();
282        String bannerType = req.getParameter("bannerType");
283        if ("unregistered".equals(bannerType)) {
284            return ConnectUpdateStatusInfo.unregistered();
285        } else if ("notreachable".equals(bannerType)) {
286            return ConnectUpdateStatusInfo.connectServerUnreachable();
287        } else if ("notvalid".equals(bannerType)) {
288            return ConnectUpdateStatusInfo.notValid();
289        } else if ("ok".equals(bannerType)) {
290            return ConnectUpdateStatusInfo.ok();
291        }
292        return getConnectUpdateStatusInfo();
293    }
294
295    /**
296     * @since 5.9.2
297     */
298    @Factory(scope = ScopeType.APPLICATION, value = "connectBannerEnabled")
299    public boolean isConnectBannerEnabled() {
300        final String testerName = Framework.getProperty("org.nuxeo.ecm.tester.name");
301        if (testerName != null && testerName.equals("Nuxeo-Selenium-Tester")) {
302            // disable banner when running selenium tests
303            return false;
304        }
305        return true;
306    }
307
308    @Factory(scope = ScopeType.APPLICATION, value = "connectUpdateStatusInfo")
309    public ConnectUpdateStatusInfo getConnectUpdateStatusInfo() {
310        if (connectionStatusCache == null) {
311            if (!isRegistered()) {
312                connectionStatusCache = ConnectUpdateStatusInfo.unregistered();
313            } else {
314                if (isConnectBannerEnabled() && isConnectServerReachable()) {
315                    if (getStatus().isError()) {
316                        connectionStatusCache = ConnectUpdateStatusInfo.connectServerUnreachable();
317                    } else {
318                        if (ConnectStatusHolder.instance().getStatus().status() == SubscriptionStatusType.OK) {
319                            connectionStatusCache = ConnectUpdateStatusInfo.ok();
320                        } else {
321                            connectionStatusCache = ConnectUpdateStatusInfo.notValid();
322                        }
323                    }
324                } else {
325                    connectionStatusCache = ConnectUpdateStatusInfo.connectServerUnreachable();
326                }
327            }
328        }
329        return connectionStatusCache;
330    }
331
332    @Factory("nuxeoConnectUrl")
333    public String getConnectServerUrl() {
334        return ConnectUrlConfig.getBaseUrl();
335    }
336
337}