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